VYPR
High severityNVD Advisory· Published Mar 6, 2026· Updated Mar 6, 2026

Locutus: Remote Code Execution (RCE) in locutus call_user_func_array due to Code Injection

CVE-2026-29091

Description

Locutus brings stdlibs of other programming languages to JavaScript for educational purposes. Prior to version 3.0.0, a remote code execution (RCE) flaw was discovered in the locutus project, specifically within the call_user_func_array function implementation. The vulnerability allows an attacker to inject arbitrary JavaScript code into the application's runtime environment. This issue stems from an insecure implementation of the call_user_func_array function (and its wrapper call_user_func), which fails to properly validate all components of a callback array before passing them to eval(). This issue has been patched in version 3.0.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
locutusnpm
< 3.0.03.0.0

Affected products

1

Patches

1
977a1fb16944

feat: Complete TypeScript migration of all source files (#535)

https://github.com/locutusjs/locutusKevin van ZonneveldMar 3, 2026via ghsa
300 files changed · +12050 4235
  • biome.json+5 0 modified
    @@ -9,11 +9,13 @@
           "!**/*.html",
           "!index.d.ts",
           "!test/generated",
    +      "!test/util/type-contracts.generated.d.ts",
           "!website",
           "!dist",
           "!node_modules",
           "!.yarn",
           "!src/php/_helpers/_bc.js",
    +      "!src/php/_helpers/_bc.ts",
           "!src/php/bc"
         ]
       },
    @@ -69,6 +71,7 @@
           },
           "security": { "noGlobalEval": "error" },
           "style": {
    +        "noNonNullAssertion": "error",
             "noYodaExpression": "error",
             "useArrayLiterals": "error",
             "useBlockStatements": "error",
    @@ -90,6 +93,7 @@
             "noDuplicateObjectKeys": "error",
             "noDuplicateParameters": "error",
             "noEmptyBlockStatements": "error",
    +        "noExplicitAny": "error",
             "noFallthroughSwitchClause": "error",
             "noFunctionAssign": "error",
             "noGlobalAssign": "error",
    @@ -103,6 +107,7 @@
             "noShadowRestrictedNames": "error",
             "noSparseArray": "error",
             "noTemplateCurlyInString": "error",
    +        "noTsIgnore": "error",
             "noUnsafeNegation": "error",
             "noUselessRegexBackrefs": "error",
             "noVar": "error",
    
  • .changeset/typescript-migration.md+14 0 added
    @@ -0,0 +1,14 @@
    +---
    +"locutus": major
    +---
    +
    +TypeScript migration: all source files converted from CJS to TypeScript with named exports.
    +
    +Breaking changes:
    +- Functions now use named exports instead of default exports
    +  - ESM: `import { sort } from 'locutus/php/array/sort'` (was: `import sort from ...`)
    +  - CJS: use named access (`const { sort } = require('locutus/php/array/sort')`)
    +- The `ini_get` dependency is no longer required — functions read ini values inline via optional chaining on `globalThis.$locutus`
    +- Repository tooling now runs as native ESM (`"type": "module"`), and Node script invocations no longer need typeless/strip-type flags.
    +  - Published package now ships dual runtime outputs: ESM for `import` and CJS for `require(...)` deep imports.
    +- Minimum supported Node.js version is now `22` (`engines.node: >= 22`).
    
  • docs/non-php-api-signatures.snapshot+177 0 added
    @@ -0,0 +1,177 @@
    +src/_util/headerFormatter.ts :: export function checkAll(srcDir: string): CheckAllResult
    +src/_util/headerFormatter.ts :: export function checkFile(filepath: string): CheckResult
    +src/_util/headerFormatter.ts :: export function formatAll(srcDir: string): FormatAllResult
    +src/_util/headerFormatter.ts :: export function formatFile(filepath: string): FormatFileResult
    +src/_util/headerFormatter.ts :: export function formatHeaderLine(key: string, value: string, longestKeyLength: number): string
    +src/_util/headerFormatter.ts :: export function formatHeaders(content: string): FormatResult
    +src/_util/headerFormatter.ts :: export function parseHeaderLine(line: string): ParsedLine
    +src/_util/headerSchema.ts :: export function getValidHeaderKeys(): string[]
    +src/_util/headerSchema.ts :: export function isValidHeaderKey(key: string): boolean
    +src/_util/headerSchema.ts :: export function validateHeaderKeys(headKeys: unknown, filepath: string): HeaderKeys
    +src/_util/headerSchema.ts :: export type HeaderKeys = z.infer<typeof HeaderKeysSchema>
    +src/awk/builtin/cos.ts :: export function cos(x: number): number
    +src/awk/builtin/exp.ts :: export function exp(x: number): number
    +src/awk/builtin/int.ts :: export function int(x: number): number
    +src/awk/builtin/length.ts :: export function length(s: string): number
    +src/awk/builtin/log.ts :: export function log(x: number): number
    +src/awk/builtin/sin.ts :: export function sin(x: number): number
    +src/awk/builtin/sqrt.ts :: export function sqrt(x: number): number
    +src/awk/builtin/substr.ts :: export function substr(str: string, start: number, len?: number): string
    +src/awk/builtin/tolower.ts :: export function tolower(s: string): string
    +src/awk/builtin/toupper.ts :: export function toupper(s: string): string
    +src/c/ctype/isalnum.ts :: export function isalnum(c: string): boolean
    +src/c/ctype/isalpha.ts :: export function isalpha(c: string): boolean
    +src/c/ctype/isdigit.ts :: export function isdigit(c: string): boolean
    +src/c/ctype/islower.ts :: export function islower(c: string): boolean
    +src/c/ctype/isspace.ts :: export function isspace(c: string): boolean
    +src/c/ctype/isupper.ts :: export function isupper(c: string): boolean
    +src/c/ctype/tolower.ts :: export function tolower(c: string): string
    +src/c/ctype/toupper.ts :: export function toupper(c: string): string
    +src/c/math/abs.ts :: export function abs(mixedNumber: number): number
    +src/c/math/frexp.ts :: export function frexp(arg: number): [number, number]
    +src/c/stdio/sprintf.ts :: export function sprintf(format: string, ...args: unknown[]): string
    +src/c/stdlib/atof.ts :: export function atof(str: string): number
    +src/c/stdlib/atoi.ts :: export function atoi(str: string): number
    +src/c/string/strcat.ts :: export function strcat(dest: string, src: string): string
    +src/c/string/strchr.ts :: export function strchr(str: string, c: string): string | null
    +src/c/string/strcmp.ts :: export function strcmp(str1: string, str2: string): number
    +src/c/string/strlen.ts :: export function strlen(str: string): number
    +src/c/string/strstr.ts :: export function strstr(haystack: string, needle: string): string | null
    +src/clojure/Math/abs.ts :: export function abs(x: number): number
    +src/clojure/Math/ceil.ts :: export function ceil(x: number): number
    +src/clojure/Math/floor.ts :: export function floor(x: number): number
    +src/clojure/string/blank.ts :: export function blank(s: string): boolean
    +src/clojure/string/lower_case.ts :: export function lower_case(s: string): string
    +src/clojure/string/reverse.ts :: export function reverse(s: string): string
    +src/clojure/string/trim.ts :: export function trim(s: string): string
    +src/clojure/string/upper_case.ts :: export function upper_case(s: string): string
    +src/elixir/Float/ceil.ts :: export function ceil(x: number): number
    +src/elixir/Float/floor.ts :: export function floor(x: number): number
    +src/elixir/Kernel/abs.ts :: export function abs(x: number): number
    +src/elixir/String/downcase.ts :: export function downcase(string: string): string
    +src/elixir/String/length.ts :: export function length(string: string): number
    +src/elixir/String/reverse.ts :: export function reverse(string: string): string
    +src/elixir/String/upcase.ts :: export function upcase(string: string): string
    +src/golang/strconv/Atoi.ts :: export function Atoi(s: unknown): [number, Error | null]
    +src/golang/strconv/FormatBool.ts :: export function FormatBool(b: boolean): string
    +src/golang/strconv/FormatInt.ts :: export function FormatInt(i: number, base: number): string
    +src/golang/strconv/Itoa.ts :: export function Itoa(i: number): string
    +src/golang/strconv/ParseBool.ts :: export function ParseBool(str: string): [boolean, Error | null]
    +src/golang/strconv/ParseInt.ts :: export function ParseInt(s: string, base: number, _bitSize: number): [number, Error | null]
    +src/golang/strings/Compare.ts :: export function Compare(a: string, b: string): number
    +src/golang/strings/Contains.ts :: export function Contains(s: string, substr: string): boolean
    +src/golang/strings/ContainsAny.ts :: export function ContainsAny(s: string, chars: string): boolean | false
    +src/golang/strings/Count.ts :: export function Count(s: string, sep: string): number
    +src/golang/strings/EqualFold.ts :: export function EqualFold(s: string, t: string): boolean
    +src/golang/strings/Fields.ts :: export function Fields(s: string): string[]
    +src/golang/strings/HasPrefix.ts :: export function HasPrefix(s: string, prefix: string): boolean
    +src/golang/strings/HasSuffix.ts :: export function HasSuffix(s: string, suffix: string): boolean
    +src/golang/strings/Index2.ts :: export function Index(s: string, sep: string): number
    +src/golang/strings/IndexAny.ts :: export function IndexAny(s: string, chars: string): number
    +src/golang/strings/Join.ts :: export function Join(elems: unknown[] | unknown, sep: string): string
    +src/golang/strings/LastIndex.ts :: export function LastIndex(s: string, sep: string): number
    +src/golang/strings/LastIndexAny.ts :: export function LastIndexAny(s: string, chars: string): number
    +src/golang/strings/Repeat.ts :: export function Repeat(s: string, count: number): string
    +src/golang/strings/Replace.ts :: export function Replace(s: string, old: string, newStr: string, n: number): string
    +src/golang/strings/Split.ts :: export function Split(s: string, sep: string): string[]
    +src/golang/strings/ToLower.ts :: export function ToLower(s: string): string
    +src/golang/strings/ToUpper.ts :: export function ToUpper(s: string): string
    +src/golang/strings/Trim.ts :: export function Trim(s: string, cutset: string): string
    +src/golang/strings/TrimLeft.ts :: export function TrimLeft(s: string, cutset: string): string
    +src/golang/strings/TrimPrefix.ts :: export function TrimPrefix(s: string, prefix: string): string
    +src/golang/strings/TrimRight.ts :: export function TrimRight(s: string, cutset: string): string
    +src/golang/strings/TrimSpace.ts :: export function TrimSpace(s: string): string
    +src/golang/strings/TrimSuffix.ts :: export function TrimSuffix(s: string, suffix: string): string
    +src/julia/Base/abs.ts :: export function abs(x: number): number
    +src/julia/Base/ceil.ts :: export function ceil(x: number): number
    +src/julia/Base/floor.ts :: export function floor(x: number): number
    +src/julia/Base/lowercase.ts :: export function lowercase(s: string): string
    +src/julia/Base/uppercase.ts :: export function uppercase(s: string): string
    +src/lua/math/abs.ts :: export function abs(x: number): number
    +src/lua/math/ceil.ts :: export function ceil(x: number): number
    +src/lua/math/cos.ts :: export function cos(x: number): number
    +src/lua/math/floor.ts :: export function floor(x: number): number
    +src/lua/math/max.ts :: export function max(...args: number[]): number
    +src/lua/math/min.ts :: export function min(...args: number[]): number
    +src/lua/math/sin.ts :: export function sin(x: number): number
    +src/lua/math/sqrt.ts :: export function sqrt(x: number): number
    +src/lua/string/len.ts :: export function len(s: string): number
    +src/lua/string/lower.ts :: export function lower(s: string): string
    +src/lua/string/rep.ts :: export function rep(s: string, n: number): string
    +src/lua/string/reverse.ts :: export function reverse(s: string): string
    +src/lua/string/sub.ts :: export function sub(s: string, i: number, j?: number): string
    +src/lua/string/upper.ts :: export function upper(s: string): string
    +src/perl/POSIX/ceil.ts :: export function ceil(x: number): number
    +src/perl/POSIX/floor.ts :: export function floor(x: number): number
    +src/perl/core/lc.ts :: export function lc(str: string): string
    +src/perl/core/length.ts :: export function length(str: string | null | undefined): number | undefined
    +src/perl/core/reverse.ts :: export function reverse(str: string): string
    +src/perl/core/substr.ts :: export function substr(str: string, offset: number, length?: number): string
    +src/perl/core/uc.ts :: export function uc(str: string): string
    +src/python/math/ceil.ts :: export function ceil(x: number): number
    +src/python/math/exp.ts :: export function exp(x: number): number
    +src/python/math/fabs.ts :: export function fabs(x: number): number
    +src/python/math/factorial.ts :: export function factorial(n: number | string): number
    +src/python/math/floor.ts :: export function floor(x: number): number
    +src/python/math/gcd.ts :: export function gcd(a: number | string, b: number | string): number
    +src/python/math/isfinite.ts :: export function isfinite(x: number): boolean
    +src/python/math/isinf.ts :: export function isinf(x: number): boolean
    +src/python/math/isnan.ts :: export function isnan(x: number): boolean
    +src/python/math/log.ts :: export function log(x: number, base: number): number
    +src/python/math/log10.ts :: export function log10(x: number): number
    +src/python/math/log2.ts :: export function log2(x: number): number
    +src/python/math/pow.ts :: export function pow(x: number, y: number): number
    +src/python/math/sqrt.ts :: export function sqrt(x: number): number
    +src/python/math/trunc.ts :: export function trunc(x: number): number
    +src/python/string/ascii_letters.ts :: export function ascii_letters(): 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    +src/python/string/ascii_lowercase.ts :: export function ascii_lowercase(): 'abcdefghijklmnopqrstuvwxyz'
    +src/python/string/ascii_uppercase.ts :: export function ascii_uppercase(): 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    +src/python/string/capwords.ts :: export function capwords(str: string): string
    +src/python/string/digits.ts :: export function digits(): '0123456789'
    +src/python/string/hexdigits.ts :: export function hexdigits(): '0123456789abcdefABCDEF'
    +src/python/string/octdigits.ts :: export function octdigits(): '01234567'
    +src/python/string/printable.ts :: export function printable(): '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
    +src/python/string/punctuation.ts :: export function punctuation(): '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
    +src/python/string/whitespace.ts :: export function whitespace(): ' \t\n\r\x0b\x0c'
    +src/r/base/abs.ts :: export function abs(x: number): number
    +src/r/base/ceiling.ts :: export function ceiling(x: number): number
    +src/r/base/floor.ts :: export function floor(x: number): number
    +src/r/base/max.ts :: export function max(...args: number[]): number
    +src/r/base/min.ts :: export function min(...args: number[]): number
    +src/r/base/nchar.ts :: export function nchar(x: string): number
    +src/r/base/round.ts :: export function round(x: number, digits?: number): number
    +src/r/base/sqrt.ts :: export function sqrt(x: number): number
    +src/r/base/tolower.ts :: export function tolower(x: string): string
    +src/r/base/toupper.ts :: export function toupper(x: string): string
    +src/ruby/Array/compact.ts :: export function compact<T>(arr: Array<T | null | undefined> | unknown): T[]
    +src/ruby/Array/first.ts :: export function first<T>(arr: T[] | unknown, n?: number): T | T[] | undefined
    +src/ruby/Array/flatten.ts :: export function flatten(arr: unknown[] | unknown, depth?: number): unknown[]
    +src/ruby/Array/last.ts :: export function last<T>(arr: T[] | unknown, n?: number): T | T[] | undefined
    +src/ruby/Array/sample.ts :: export function sample(arr: unknown[], n?: number): unknown | unknown[] | undefined
    +src/ruby/Array/uniq.ts :: export function uniq<T>(arr: T[] | unknown): T[]
    +src/ruby/Math/acos.ts :: export function acos(arg: number): number
    +src/ruby/Math/asin.ts :: export function asin(x: number): number
    +src/ruby/Math/atan.ts :: export function atan(x: number): number
    +src/ruby/Math/cbrt.ts :: export function cbrt(x: number): number
    +src/ruby/Math/cos.ts :: export function cos(x: number): number
    +src/ruby/Math/cosh.ts :: export function cosh(x: number): number
    +src/ruby/Math/exp.ts :: export function exp(x: number): number
    +src/ruby/Math/log.ts :: export function log(x: number): number
    +src/ruby/Math/log10.ts :: export function log10(x: number): number
    +src/ruby/Math/log2.ts :: export function log2(x: number): number
    +src/ruby/Math/sin.ts :: export function sin(x: number): number
    +src/ruby/Math/sinh.ts :: export function sinh(x: number): number
    +src/ruby/Math/sqrt.ts :: export function sqrt(x: number): number
    +src/ruby/Math/tan.ts :: export function tan(x: number): number
    +src/ruby/Math/tanh.ts :: export function tanh(x: number): number
    +src/ruby/String/capitalize.ts :: export function capitalize(str: string): string
    +src/ruby/String/chomp.ts :: export function chomp(str: string, separator?: string): string
    +src/ruby/String/chop.ts :: export function chop(str: string): string
    +src/ruby/String/downcase.ts :: export function downcase(str: string): string
    +src/ruby/String/end_with.ts :: export function end_with(str: string, suffix: string): boolean
    +src/ruby/String/include.ts :: export function include(str: string, other: string): boolean
    +src/ruby/String/length.ts :: export function length(str: string): number
    +src/ruby/String/reverse.ts :: export function reverse(str: string): string
    +src/ruby/String/start_with.ts :: export function start_with(str: string, prefix: string): boolean
    +src/ruby/String/strip.ts :: export function strip(str: string): string
    +src/ruby/String/upcase.ts :: export function upcase(str: string): string
    
  • docs/php-api-signatures.snapshot+453 0 added
    @@ -0,0 +1,453 @@
    +src/php/_helpers/_arrayPointers.ts :: export function getArrayLikeLength<T>(target: PhpArrayLike<T>): number
    +src/php/_helpers/_arrayPointers.ts :: export function getEntryAtCursor<T>(target: PhpArrayLike<T>, cursor: number): [string | number, T] | null
    +src/php/_helpers/_arrayPointers.ts :: export function getPointerState<T>(target: PhpArrayLike<T>, initialize = true): PointerState | null
    +src/php/_helpers/_bc.ts :: export function _bc(): ReturnType<typeof createBcLibrary>
    +src/php/_helpers/_callbackResolver.ts :: export function resolveNumericComparator< TLeft extends CallbackValue = CallbackValue, TRight extends CallbackValue = CallbackValue, >( callback: PhpCallableDescriptor<[TLeft, TRight], NumericLike>, invalidMessage: string, ): (left: TLeft, right: TRight) => number
    +src/php/_helpers/_callbackResolver.ts :: export function resolvePhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = CallbackValue>( callback: PhpCallableDescriptor<TArgs, TResult>, options: CallbackResolverOptions, ): ResolvedCallback<TArgs, TResult>
    +src/php/_helpers/_ctypePattern.ts :: export function getCtypePattern(key: string): RegExp | undefined
    +src/php/_helpers/_phpCastString.ts :: export function _phpCastString(value: CastStringValue): string
    +src/php/_helpers/_phpRuntimeState.ts :: export function ensurePhpRuntimeState(): PhpRuntimeState
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpGlobalCallable<TArgs extends PhpInput[] = PhpInput[], TResult = PhpInput>( key: string, ): PhpCallable<TArgs, TResult> | undefined
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpGlobalEntry(key: string): PhpInput | undefined
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpGlobalEntry<TKey extends keyof PhpGlobalKnownEntryMap>( key: TKey, ): PhpGlobalKnownEntryMap[TKey] | undefined
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpGlobalScope(): PhpAssoc<PhpInput>
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpLocaleGroup(category: string, groupKey: string): PhpAssoc<PhpInput> | undefined
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpObjectEntry(value: PhpInput, key: string): PhpInput | undefined
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpRuntimeBoolean(key: PhpRuntimeBooleanKey, fallback: boolean): boolean
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpRuntimeBoolean(key: string, fallback: boolean): boolean
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpRuntimeEntry(key: string): PhpInput | undefined
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpRuntimeEntry<TKey extends keyof PhpRuntimeKnownEntryMap>( key: TKey, ): PhpRuntimeKnownEntryMap[TKey] | undefined
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpRuntimeNumber(key: PhpRuntimeNumberKey, fallback: number): number
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpRuntimeNumber(key: string, fallback: number): number
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpRuntimeString(key: PhpRuntimeStringKey, fallback: string): string
    +src/php/_helpers/_phpRuntimeState.ts :: export function getPhpRuntimeString(key: string, fallback: string): string
    +src/php/_helpers/_phpRuntimeState.ts :: export function setPhpGlobalEntry(key: string, value: PhpInput): void
    +src/php/_helpers/_phpRuntimeState.ts :: export function setPhpLocaleDefault(localeDefault: string): void
    +src/php/_helpers/_phpRuntimeState.ts :: export function setPhpObjectEntry(value: PhpInput, key: string, entry: PhpInput): boolean
    +src/php/_helpers/_phpRuntimeState.ts :: export function setPhpRuntimeEntry(key: string, value: PhpInput): void
    +src/php/_helpers/_phpRuntimeState.ts :: export function setPhpRuntimeEntry<TKey extends keyof PhpRuntimeKnownEntryMap>( key: TKey, value: PhpRuntimeKnownEntryMap[TKey], ): void
    +src/php/_helpers/_phpRuntimeState.ts :: export interface PhpRuntimeState { ini: PhpAssoc<IniEntry | undefined> locales: PhpAssoc<LocaleEntry | undefined> localeCategories: LocaleCategoryMap pointers: PhpList<PhpInput> locale_default: string | undefined }
    +src/php/_helpers/_phpTypes.ts :: export function assertIsNumericLike( value: PhpInput, message = 'Expected numeric-like value', ): asserts value is NumericLike
    +src/php/_helpers/_phpTypes.ts :: export function assertIsObjectLike( value: PhpInput, message = 'Expected object-like value', ): asserts value is PhpArrayLike<PhpInput>
    +src/php/_helpers/_phpTypes.ts :: export function assertIsPhpAssocObject( value: PhpInput, message = 'Expected associative object value', ): asserts value is PhpAssoc<PhpInput>
    +src/php/_helpers/_phpTypes.ts :: export function assertIsPhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput>( value: PhpInput, message = 'Expected callable value', ): asserts value is PhpCallable<TArgs, TResult>
    +src/php/_helpers/_phpTypes.ts :: export function assertIsPhpKey(value: PhpInput, message = 'Expected key value'): asserts value is PhpKey
    +src/php/_helpers/_phpTypes.ts :: export function assertIsPhpList(value: PhpInput, message = 'Expected list value'): asserts value is PhpList<PhpInput>
    +src/php/_helpers/_phpTypes.ts :: export function isNumericLike(value: PhpInput): value is NumericLike
    +src/php/_helpers/_phpTypes.ts :: export function isObjectLike(value: PhpInput): value is PhpArrayLike<PhpInput>
    +src/php/_helpers/_phpTypes.ts :: export function isPhpArrayObject<T = PhpInput>(value: PhpInput): value is PhpAssoc<T>
    +src/php/_helpers/_phpTypes.ts :: export function isPhpAssocObject<T = PhpInput>(value: PhpInput): value is PhpAssoc<T>
    +src/php/_helpers/_phpTypes.ts :: export function isPhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput>( value: PhpInput, ): value is PhpCallable<TArgs, TResult>
    +src/php/_helpers/_phpTypes.ts :: export function isPhpCallableDescriptor<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput>( value: PhpInput, ): value is PhpCallableDescriptor<TArgs, TResult>
    +src/php/_helpers/_phpTypes.ts :: export function isPhpKey(value: PhpInput): value is PhpKey
    +src/php/_helpers/_phpTypes.ts :: export function isPhpList<T = PhpInput>(value: PhpInput): value is PhpList<T>
    +src/php/_helpers/_phpTypes.ts :: export function isPhpNullish(value: PhpInput): value is PhpNullish
    +src/php/_helpers/_phpTypes.ts :: export function isPhpScalar(value: PhpInput): value is PhpScalar
    +src/php/_helpers/_phpTypes.ts :: export function toPhpArrayObject<T = PhpInput>(value: PhpInput): PhpAssoc<T>
    +src/php/_helpers/_phpTypes.ts :: export interface PhpRecursiveAssoc { [key: string]: PhpRecursiveValue }
    +src/php/_helpers/_phpTypes.ts :: export interface PhpRecursiveList extends Array<PhpRecursiveValue> {}
    +src/php/_helpers/_phpTypes.ts :: export interface PhpRuntimeAssoc { [key: string]: PhpRuntimeValue }
    +src/php/_helpers/_phpTypes.ts :: export interface PhpRuntimeList extends Array<PhpRuntimeValue> {}
    +src/php/_helpers/_phpTypes.ts :: export type NumericLike = number | bigint | string
    +src/php/_helpers/_phpTypes.ts :: export type PhpArrayLike<T = PhpInput> = PhpList<T> | PhpAssoc<T>
    +src/php/_helpers/_phpTypes.ts :: export type PhpAssoc<T = PhpInput> = { [key: string]: T }
    +src/php/_helpers/_phpTypes.ts :: export type PhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = ( ...args: TArgs ) => TResult
    +src/php/_helpers/_phpTypes.ts :: export type PhpCallableArgs = PhpInput[]
    +src/php/_helpers/_phpTypes.ts :: export type PhpCallableDescriptor<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = | string | PhpCallable<TArgs, TResult> | PhpCallableTuple<TArgs, TResult>
    +src/php/_helpers/_phpTypes.ts :: export type PhpCallableScope = PhpInput
    +src/php/_helpers/_phpTypes.ts :: export type PhpCallableTuple<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = readonly [ PhpCallableScope, string | PhpCallable<TArgs, TResult>, ]
    +src/php/_helpers/_phpTypes.ts :: export type PhpComparatorDescriptor<T> = PhpCallableDescriptor<[T, T], NumericLike>
    +src/php/_helpers/_phpTypes.ts :: export type PhpContainer<T = PhpInput> = PhpList<T> | PhpAssoc<T>
    +src/php/_helpers/_phpTypes.ts :: export type PhpFunctionValue = (...args: PhpInput[]) => PhpInput
    +src/php/_helpers/_phpTypes.ts :: export type PhpInput = {} | PhpNullish
    +src/php/_helpers/_phpTypes.ts :: export type PhpKey = string | number
    +src/php/_helpers/_phpTypes.ts :: export type PhpKeyComparatorDescriptor = PhpCallableDescriptor<[string, string], NumericLike>
    +src/php/_helpers/_phpTypes.ts :: export type PhpList<T = PhpInput> = T[]
    +src/php/_helpers/_phpTypes.ts :: export type PhpLiteral = PhpScalar | null
    +src/php/_helpers/_phpTypes.ts :: export type PhpNullish = null | undefined
    +src/php/_helpers/_phpTypes.ts :: export type PhpPrimitive = PhpScalar | bigint
    +src/php/_helpers/_phpTypes.ts :: export type PhpReadonlyArrayLike<T = PhpInput> = PhpReadonlyList<T> | PhpReadonlyAssoc<T>
    +src/php/_helpers/_phpTypes.ts :: export type PhpReadonlyAssoc<T = PhpInput> = Readonly<PhpAssoc<T>>
    +src/php/_helpers/_phpTypes.ts :: export type PhpReadonlyList<T = PhpInput> = readonly T[]
    +src/php/_helpers/_phpTypes.ts :: export type PhpRecursiveValue = PhpPrimitive | PhpNullish | PhpRecursiveList | PhpRecursiveAssoc
    +src/php/_helpers/_phpTypes.ts :: export type PhpRuntimeValue = PhpPrimitive | PhpNullish | PhpRuntimeList | PhpRuntimeAssoc | PhpFunctionValue
    +src/php/_helpers/_phpTypes.ts :: export type PhpScalar = string | number | boolean
    +src/php/_helpers/_phpTypes.ts :: export type PhpStringish = StringLike | PhpNullish
    +src/php/_helpers/_phpTypes.ts :: export type StringLike = string | number | boolean | bigint
    +src/php/_helpers/_php_cast_float.ts :: export function _php_cast_float(value: CastFloatValue): number
    +src/php/_helpers/_php_cast_int.ts :: export function _php_cast_int(value: CastIntValue): number
    +src/php/array/array_change_key_case.ts :: export function array_change_key_case(array: number | null, cs?: ChangeKeyCaseMode): false
    +src/php/array/array_change_key_case.ts :: export function array_change_key_case<TValue extends ChangeValue>( array: ArrayChangeInput<TValue>, cs?: ChangeKeyCaseMode, ): PhpArrayLike<TValue> | false
    +src/php/array/array_change_key_case.ts :: export function array_change_key_case<TValue extends ChangeValue>( array: PhpAssoc<TValue>, cs?: ChangeKeyCaseMode, ): PhpAssoc<TValue>
    +src/php/array/array_change_key_case.ts :: export function array_change_key_case<TValue extends ChangeValue>(array: TValue[], cs?: ChangeKeyCaseMode): TValue[]
    +src/php/array/array_chunk.ts :: export function array_chunk<T>( input: ArrayChunkInput<T>, size: number, preserveKeys?: boolean, ): ArrayChunkOutput<T>[] | null
    +src/php/array/array_column.ts :: export function array_column<TValue>( input: ColumnInput<TValue> | null | undefined, columnKey: string | number | null, indexKey?: string | number | null, ): ColumnOutput<TValue> | undefined
    +src/php/array/array_column.ts :: export function array_column<TValue>( input: ColumnInput<TValue>, columnKey: string | number | null, indexKey?: string | number | null, ): ColumnOutput<TValue>
    +src/php/array/array_combine.ts :: export function array_combine<TKey extends string | number, TValue>( keys: TKey[], values: TValue[], ): PhpAssoc<TValue> | false
    +src/php/array/array_count_values.ts :: export function array_count_values(array: PhpArrayLike<CountValue>): PhpAssoc<number>
    +src/php/array/array_diff.ts :: export function array_diff<TValue>( arr1: PhpArrayLike<TValue>, ...arrays: Array<PhpArrayLike<TValue>> ): PhpAssoc<TValue>
    +src/php/array/array_diff_assoc.ts :: export function array_diff_assoc<TValue>( arr1: PhpArrayLike<TValue>, ...arrays: Array<PhpArrayLike<TValue>> ): PhpAssoc<TValue>
    +src/php/array/array_diff_key.ts :: export function array_diff_key<TValue>( arr1: PhpArrayLike<TValue>, ...arrays: Array<PhpArrayLike<TValue>> ): PhpAssoc<TValue>
    +src/php/array/array_diff_uassoc.ts :: export function array_diff_uassoc<T>( arr1: PhpAssoc<T>, ...arraysAndCallback: [arr2: DiffArray<T>, ...rest: Array<DiffArray<T>>, callback: PhpKeyComparatorDescriptor] ): PhpAssoc<T>
    +src/php/array/array_diff_ukey.ts :: export function array_diff_ukey<T>( arr1: PhpAssoc<T>, ...arraysAndCallback: [arr2: DiffArray<T>, ...rest: Array<DiffArray<T>>, callback: PhpKeyComparatorDescriptor] ): PhpAssoc<T>
    +src/php/array/array_fill.ts :: export function array_fill<TValue>(startIndex: number, num: number, mixedVal: TValue): PhpAssoc<TValue>
    +src/php/array/array_fill_keys.ts :: export function array_fill_keys<TKey extends PhpKey | StringLike, TValue>( keys: PhpArrayLike<TKey>, value: TValue, ): PhpAssoc<TValue>
    +src/php/array/array_filter.ts :: export function array_filter<T>(arr: PhpAssoc<T> | PhpList<T>, func?: FilterCallback<T>): PhpAssoc<T> | PhpList<T>
    +src/php/array/array_filter.ts :: export function array_filter<T>(arr: PhpAssoc<T>, func?: AssocFilterCallback<T>): PhpAssoc<T>
    +src/php/array/array_filter.ts :: export function array_filter<T>(arr: PhpList<T>, func?: ListFilterCallback<T>): PhpList<T>
    +src/php/array/array_flip.ts :: export function array_flip<TValue extends PhpKey | StringLike>(trans: PhpArrayLike<TValue>): PhpAssoc<string>
    +src/php/array/array_intersect.ts :: export function array_intersect<TValue>( arr1: PhpArrayLike<TValue>, ...arrays: Array<PhpArrayLike<TValue>> ): PhpAssoc<TValue>
    +src/php/array/array_intersect_assoc.ts :: export function array_intersect_assoc<TValue>( arr1: PhpArrayLike<TValue>, ...arrays: Array<PhpArrayLike<TValue>> ): PhpAssoc<TValue>
    +src/php/array/array_intersect_key.ts :: export function array_intersect_key<TValue>( arr1: PhpArrayLike<TValue>, ...arrays: Array<PhpArrayLike<TValue>> ): PhpAssoc<TValue>
    +src/php/array/array_intersect_uassoc.ts :: export function array_intersect_uassoc<T>( arr1: PhpAssoc<T>, ...arraysAndCallback: [ arr2: IntersectArray<T>, ...rest: Array<IntersectArray<T>>, callback: PhpKeyComparatorDescriptor, ] ): PhpAssoc<T>
    +src/php/array/array_intersect_ukey.ts :: export function array_intersect_ukey<T>( arr1: PhpAssoc<T>, ...arraysAndCallback: [ arr2: IntersectArray<T>, ...rest: Array<IntersectArray<T>>, callback: PhpKeyComparatorDescriptor, ] ): PhpAssoc<T>
    +src/php/array/array_key_exists.ts :: export function array_key_exists<TValue>(key: string | number, search: PhpArrayLike<TValue>): boolean
    +src/php/array/array_keys.ts :: export function array_keys<T>(input: PhpAssoc<T>, searchValue?: T, argStrict?: boolean): string[]
    +src/php/array/array_map.ts :: export function array_map<TInputs extends ArrayMapInputs, TResult>( callback: PhpCallableDescriptor<ArrayMapTupleArgs<TInputs>, TResult>, ...inputArrays: TInputs ): PhpList<TResult>
    +src/php/array/array_map.ts :: export function array_map<TInputs extends ArrayMapInputs>( callback: null | undefined, ...inputArrays: TInputs ): PhpList<ArrayMapTupleArgs<TInputs>>
    +src/php/array/array_map.ts :: export function array_map<TResult = ArrayMapValue>( callback: PhpCallableDescriptor<ArrayMapCallbackArgsVariadic, TResult> | null | undefined, ...inputArrays: ArrayMapInputs ): PhpList<TResult> | PhpList<ArrayMapCallbackArgsVariadic>
    +src/php/array/array_merge.ts :: export function array_merge<T>( ...args: [first: PhpArrayLike<T>, ...rest: Array<PhpArrayLike<T>>] ): PhpList<T> | AssociativeArray<T>
    +src/php/array/array_merge.ts :: export function array_merge<T>(...args: [first: PhpArrayLike<T>, ...rest: Array<PhpArrayLike<T>>]): AssociativeArray<T>
    +src/php/array/array_merge.ts :: export function array_merge<T>(...args: [first: PhpList<T>, ...rest: Array<PhpList<T>>]): PhpList<T>
    +src/php/array/array_merge_recursive.ts :: export function array_merge_recursive(arr1: MergeObject, arr2: MergeObject): MergeObject
    +src/php/array/array_multisort.ts :: export function array_multisort(arr: SortValue[] | SortableObject, ...rest: SortArg[]): boolean
    +src/php/array/array_pad.ts :: export function array_pad<TInput extends PadValue, TPad extends PadValue>( input: TInput[] | NonArrayPadInput, padSize: number, padValue: TPad, ): Array<TInput | TPad>
    +src/php/array/array_pad.ts :: export function array_pad<TInput extends PadValue, TPad extends PadValue>( input: TInput[], padSize: number, padValue: TPad, ): Array<TInput | TPad>
    +src/php/array/array_pad.ts :: export function array_pad<TPad extends PadValue>(input: NonArrayPadInput, padSize: number, padValue: TPad): TPad[]
    +src/php/array/array_pop.ts :: export function array_pop<T>(inputArr: Record<string, T> | T[]): T | null
    +src/php/array/array_product.ts :: export function array_product(input: PhpList<ProductValue> | ProductInput): number | null
    +src/php/array/array_product.ts :: export function array_product(input: PhpList<ProductValue>): number
    +src/php/array/array_product.ts :: export function array_product(input: ProductInput): number | null
    +src/php/array/array_push.ts :: export function array_push<TValue>(inputArr: PhpArrayLike<TValue>, ...values: TValue[]): number
    +src/php/array/array_rand.ts :: export function array_rand<TValue extends RandomValue>( array: PhpArrayLike<TValue>, num: null | undefined, ): string | null
    +src/php/array/array_rand.ts :: export function array_rand<TValue extends RandomValue>( array: PhpArrayLike<TValue>, num: number, ): string | string[] | null
    +src/php/array/array_rand.ts :: export function array_rand<TValue extends RandomValue>( array: PhpArrayLike<TValue>, num?: number | null, ): string | string[] | null
    +src/php/array/array_rand.ts :: export function array_rand<TValue extends RandomValue>(array: PhpArrayLike<TValue>): string | null
    +src/php/array/array_rand.ts :: export function array_rand<TValue extends RandomValue>(array: PhpArrayLike<TValue>, num: 1): string | null
    +src/php/array/array_reduce.ts :: export function array_reduce( aInput: readonly PhpRuntimeValue[], callback: (carry: PhpRuntimeValue | null, value: PhpRuntimeValue) => PhpRuntimeValue, initial?: PhpRuntimeValue, ): PhpRuntimeValue | null
    +src/php/array/array_reduce.ts :: export function array_reduce<TValue, TCarry>( aInput: readonly TValue[], callback: (carry: TCarry, value: TValue) => TCarry, initial: TCarry, ): TCarry
    +src/php/array/array_reduce.ts :: export function array_reduce<TValue>( aInput: readonly TValue[], callback: (carry: TValue | null, value: TValue) => TValue, ): TValue | null
    +src/php/array/array_replace.ts :: export function array_replace<TValue>( arr: PhpArrayLike<TValue>, firstReplacement: PhpArrayLike<TValue> | null | undefined, ...additionalReplacements: Array<PhpArrayLike<TValue> | null | undefined> ): PhpAssoc<TValue | undefined>
    +src/php/array/array_replace_recursive.ts :: export function array_replace_recursive( arr: RecursiveReplaceTarget, ...replacements: [replacement: RecursiveReplaceTarget, ...additionalReplacements: RecursiveReplaceTarget[]] ): RecursiveReplaceTarget
    +src/php/array/array_reverse.ts :: export function array_reverse( array: ReverseValue[] | PhpAssoc<ReverseValue>, preserveKeys?: boolean, ): ReverseValue[] | PhpAssoc<ReverseValue>
    +src/php/array/array_reverse.ts :: export function array_reverse<TValue extends ReverseValue>( array: PhpAssoc<TValue>, preserveKeys: true, ): PhpAssoc<TValue>
    +src/php/array/array_reverse.ts :: export function array_reverse<TValue extends ReverseValue>( array: PhpAssoc<TValue>, preserveKeys?: false | undefined, ): PhpAssoc<TValue> | TValue[]
    +src/php/array/array_reverse.ts :: export function array_reverse<TValue extends ReverseValue>(array: TValue[], preserveKeys: true): PhpAssoc<TValue>
    +src/php/array/array_reverse.ts :: export function array_reverse<TValue extends ReverseValue>(array: TValue[], preserveKeys?: false | undefined): TValue[]
    +src/php/array/array_search.ts :: export function array_search( needle: SearchValue | RegExp, haystack: PhpAssoc<SearchValue>, argStrict?: boolean, ): string | false
    +src/php/array/array_search.ts :: export function array_search<TValue extends SearchValue>( needle: TValue | RegExp, haystack: PhpAssoc<TValue>, argStrict: true, ): string | false
    +src/php/array/array_shift.ts :: export function array_shift<T>(inputArr: T[]): T | null
    +src/php/array/array_slice.ts :: export function array_slice<TValue extends SliceValue>( arr: PhpAssoc<TValue>, offst: number, lgth?: number, preserveKeys?: boolean, ): PhpAssoc<TValue | undefined>
    +src/php/array/array_slice.ts :: export function array_slice<TValue extends SliceValue>( arr: TValue[] | PhpAssoc<TValue>, offst: number, lgth?: number, preserveKeys?: boolean, ): TValue[] | PhpAssoc<TValue | undefined>
    +src/php/array/array_slice.ts :: export function array_slice<TValue extends SliceValue>( arr: TValue[], offst: number, lgth: number | undefined, preserveKeys: true, ): TValue[] | PhpAssoc<TValue | undefined>
    +src/php/array/array_slice.ts :: export function array_slice<TValue extends SliceValue>( arr: TValue[], offst: number, lgth?: number, preserveKeys?: false | undefined, ): TValue[]
    +src/php/array/array_splice.ts :: export function array_splice<T extends SpliceValue>( arr: Array<T | undefined> | AssocArray<T>, offst: number, lgth?: number, replacement?: ReplacementValue<T>, ): Array<T | undefined> | AssocArray<T>
    +src/php/array/array_splice.ts :: export function array_splice<T extends SpliceValue>( arr: Array<T | undefined>, offst: number, lgth?: number, replacement?: ReplacementValue<T>, ): Array<T | undefined>
    +src/php/array/array_splice.ts :: export function array_splice<T extends SpliceValue>( arr: AssocArray<T>, offst: number, lgth?: number, replacement?: ReplacementValue<T>, ): Array<T | undefined> | AssocArray<T>
    +src/php/array/array_sum.ts :: export function array_sum(array: PhpArrayLike<SummableValue> | null): number | null
    +src/php/array/array_udiff.ts :: export function array_udiff<T>( arr1: PhpAssoc<T>, ...arraysAndCallback: [arr2: DiffArray<T>, ...rest: Array<DiffArray<T>>, callback: PhpComparatorDescriptor<T>] ): PhpAssoc<T>
    +src/php/array/array_udiff_assoc.ts :: export function array_udiff_assoc<T>( arr1: PhpAssoc<T>, ...arraysAndCallback: [arr2: DiffArray<T>, ...rest: Array<DiffArray<T>>, callback: PhpComparatorDescriptor<T>] ): PhpAssoc<T>
    +src/php/array/array_udiff_uassoc.ts :: export function array_udiff_uassoc<T>( arr1: PhpAssoc<T>, ...arraysAndComparators: [ arr2: DiffArray<T>, ...rest: Array<DiffArray<T>>, valueCallback: PhpComparatorDescriptor<T>, keyCallback: PhpKeyComparatorDescriptor, ] ): PhpAssoc<T>
    +src/php/array/array_uintersect.ts :: export function array_uintersect<T>( arr1: PhpAssoc<T>, ...arraysAndCallback: [ arr2: IntersectArray<T>, ...rest: Array<IntersectArray<T>>, callback: PhpComparatorDescriptor<T>, ] ): PhpAssoc<T>
    +src/php/array/array_uintersect_uassoc.ts :: export function array_uintersect_uassoc<T>( arr1: PhpAssoc<T>, ...arraysAndComparators: [ arr2: IntersectArray<T>, ...rest: Array<IntersectArray<T>>, valueCallback: PhpComparatorDescriptor<T>, keyCallback: PhpKeyComparatorDescriptor, ] ): PhpAssoc<T>
    +src/php/array/array_unique.ts :: export function array_unique<T>(inputArr: T[] | PhpAssoc<T>): PhpAssoc<T> | false
    +src/php/array/array_unshift.ts :: export function array_unshift<TValue extends UnshiftValue>(array: TValue[], ...values: TValue[]): number
    +src/php/array/array_values.ts :: export function array_values<T>(input: T[] | PhpAssoc<T>): T[]
    +src/php/array/array_walk.ts :: export function array_walk< TValue extends ArrayWalkInput = ArrayWalkInput, TUserdata extends ArrayWalkInput = ArrayWalkInput, >( array: TValue[] | PhpAssoc<TValue>, funcname: ArrayWalkCallback<number, TValue, TUserdata> | ArrayWalkCallback<string, TValue, TUserdata>, userdata?: TUserdata, ): boolean
    +src/php/array/array_walk.ts :: export function array_walk< TValue extends ArrayWalkInput = ArrayWalkInput, TUserdata extends ArrayWalkInput = ArrayWalkInput, >(array: PhpAssoc<TValue>, funcname: ArrayWalkCallback<string, TValue, TUserdata>, userdata?: TUserdata): boolean
    +src/php/array/array_walk.ts :: export function array_walk< TValue extends ArrayWalkInput = ArrayWalkInput, TUserdata extends ArrayWalkInput = ArrayWalkInput, >(array: TValue[], funcname: ArrayWalkCallback<number, TValue, TUserdata>, userdata?: TUserdata): boolean
    +src/php/array/array_walk_recursive.ts :: export function array_walk_recursive< TValue extends RecursiveWalkInput = RecursiveWalkInput, TUserdata extends RecursiveWalkInput = RecursiveWalkInput, >( array: RecursiveWalkAssoc<TValue>, funcname: ArrayWalkRecursiveCallback<string, TValue, TUserdata>, userdata?: TUserdata, ): boolean
    +src/php/array/array_walk_recursive.ts :: export function array_walk_recursive< TValue extends RecursiveWalkInput = RecursiveWalkInput, TUserdata extends RecursiveWalkInput = RecursiveWalkInput, >( array: RecursiveWalkCollection<TValue>, funcname: | ArrayWalkRecursiveCallback<number, TValue, TUserdata> | ArrayWalkRecursiveCallback<string, TValue, TUserdata>, userdata?: TUserdata, ): boolean
    +src/php/array/array_walk_recursive.ts :: export function array_walk_recursive< TValue extends RecursiveWalkInput = RecursiveWalkInput, TUserdata extends RecursiveWalkInput = RecursiveWalkInput, >( array: RecursiveWalkList<TValue>, funcname: ArrayWalkRecursiveCallback<number, TValue, TUserdata>, userdata?: TUserdata, ): boolean
    +src/php/array/arsort.ts :: export function arsort<T extends SortValue>( inputArr: Record<string, T>, sortFlags?: SortFlag, ): boolean | Record<string, T>
    +src/php/array/asort.ts :: export function asort<T extends SortValue>( inputArr: Record<string, T>, sortFlags?: SortFlag, ): boolean | Record<string, T>
    +src/php/array/count.ts :: export function count(mixedVar: Countable | null | undefined, mode: CountMode = 0): number
    +src/php/array/count.ts :: export function count(mixedVar: Countable, mode?: CountMode): number
    +src/php/array/count.ts :: export function count(mixedVar: null | undefined, mode?: CountMode): 0
    +src/php/array/count.ts :: export type CountMode = 0 | 1 | 'COUNT_NORMAL' | 'COUNT_RECURSIVE'
    +src/php/array/count.ts :: export type Countable = CountableList | CountableAssoc
    +src/php/array/current.ts :: export function current<T>(arr: PhpArrayLike<T>): T | false
    +src/php/array/each.ts :: export function each<T>(arr: PhpArrayLike<T>): EachResult<T>
    +src/php/array/end.ts :: export function end<T>(arr: PhpArrayLike<T>): T | false
    +src/php/array/in_array.ts :: export function in_array(needle: InArrayValue, haystack: PhpArrayLike<InArrayValue>, argStrict?: boolean): boolean
    +src/php/array/in_array.ts :: export function in_array<TNeedle extends InArrayValue>( needle: TNeedle, haystack: PhpArrayLike<TNeedle>, argStrict: true, ): boolean
    +src/php/array/key.ts :: export function key<T>(arr: PhpArrayLike<T>): string | number | false
    +src/php/array/krsort.ts :: export function krsort<T>(inputArr: Record<string, T>, sortFlags?: SortFlag): boolean | Record<string, T>
    +src/php/array/ksort.ts :: export function ksort<T>(inputArr: Record<string, T>, sortFlags?: SortFlag): boolean | Record<string, T>
    +src/php/array/natcasesort.ts :: export function natcasesort<T extends string | number>(inputArr: Record<string, T>): boolean | Record<string, T>
    +src/php/array/natsort.ts :: export function natsort<T extends string | number>(inputArr: Record<string, T>): boolean | Record<string, T>
    +src/php/array/next.ts :: export function next<T>(arr: PhpArrayLike<T>): T | false
    +src/php/array/pos.ts :: export function pos<T>(arr: T[] | Record<string, T>): T | false
    +src/php/array/prev.ts :: export function prev<T>(arr: PhpArrayLike<T>): T | false
    +src/php/array/range.ts :: export function range(low: string | number, high: string | number, step?: number): Array<string | number>
    +src/php/array/reset.ts :: export function reset<T>(arr: PhpArrayLike<T>): T | false
    +src/php/array/rsort.ts :: export function rsort<T extends SortValue>( inputArr: Record<string, T>, sortFlags?: SortFlag, ): boolean | Record<string, T>
    +src/php/array/shuffle.ts :: export function shuffle<T>(inputArr: Record<string, T>): boolean | Record<string, T> | T[]
    +src/php/array/sizeof.ts :: export function sizeof(mixedVar: Countable, mode?: CountMode): number
    +src/php/array/sort.ts :: export function sort<T extends SortValue>( inputArr: Record<string, T>, sortFlags?: SortFlag, ): boolean | Record<string, T>
    +src/php/array/uasort.ts :: export function uasort<T>( this: PhpAssoc<SortContextValue>, inputArr: Record<string, T>, sorter: PhpComparatorDescriptor<T>, ): boolean | Record<string, T>
    +src/php/array/uksort.ts :: export function uksort<T>( this: PhpAssoc<SortContextValue> & { window?: PhpAssoc<SortContextValue> }, inputArr: Record<string, T>, sorter: PhpKeyComparatorDescriptor, ): boolean | Record<string, T>
    +src/php/array/usort.ts :: export function usort<T>( this: PhpAssoc<SortContextValue>, inputArr: Record<string, T>, sorter: PhpComparatorDescriptor<T>, ): boolean | Record<string, T>
    +src/php/bc/bcadd.ts :: export function bcadd(leftOperand: string, rightOperand: string, scale?: number): string
    +src/php/bc/bccomp.ts :: export function bccomp(leftOperand: string | number, rightOperand: string | number, scale?: number): number
    +src/php/bc/bcdiv.ts :: export function bcdiv(leftOperand: string | number, rightOperand: string | number, scale?: number): string
    +src/php/bc/bcmul.ts :: export function bcmul(leftOperand: string, rightOperand: string, scale?: number): string
    +src/php/bc/bcround.ts :: export function bcround(val: number, precision: number): string
    +src/php/bc/bcscale.ts :: export function bcscale(scale: number | string): boolean
    +src/php/bc/bcsub.ts :: export function bcsub(leftOperand: string, rightOperand: string, scale?: number): string
    +src/php/ctype/ctype_alnum.ts :: export function ctype_alnum(text: string): boolean
    +src/php/ctype/ctype_alpha.ts :: export function ctype_alpha(text: string): boolean
    +src/php/ctype/ctype_cntrl.ts :: export function ctype_cntrl(text: string): boolean
    +src/php/ctype/ctype_digit.ts :: export function ctype_digit(text: string): boolean
    +src/php/ctype/ctype_graph.ts :: export function ctype_graph(text: string): boolean
    +src/php/ctype/ctype_lower.ts :: export function ctype_lower(text: string): boolean
    +src/php/ctype/ctype_print.ts :: export function ctype_print(text: string): boolean
    +src/php/ctype/ctype_punct.ts :: export function ctype_punct(text: string): boolean
    +src/php/ctype/ctype_space.ts :: export function ctype_space(text: string): boolean
    +src/php/ctype/ctype_upper.ts :: export function ctype_upper(text: string): boolean
    +src/php/ctype/ctype_xdigit.ts :: export function ctype_xdigit(text: string): boolean
    +src/php/datetime/checkdate.ts :: export function checkdate(m: number, d: number, y: number): boolean
    +src/php/datetime/date.ts :: export function date(format: string, timestamp?: number | Date | string): string
    +src/php/datetime/date_parse.ts :: export function date_parse(date: string): DateParseResult | false
    +src/php/datetime/getdate.ts :: export function getdate(timestamp?: number | string | Date): GetDateResult
    +src/php/datetime/gettimeofday.ts :: export function gettimeofday(returnFloat?: boolean): number | GetTimeOfDayObject
    +src/php/datetime/gmdate.ts :: export function gmdate(format: string, timestamp?: string | number | Date): string
    +src/php/datetime/gmmktime.ts :: export function gmmktime( hour?: number | string, minute?: number | string, second?: number | string, month?: number | string, day?: number | string, year?: number | string, ): number | false
    +src/php/datetime/gmstrftime.ts :: export function gmstrftime(format: string, timestamp?: number | Date): string
    +src/php/datetime/idate.ts :: export function idate(format?: string, timestamp?: number | string | Date): number
    +src/php/datetime/microtime.ts :: export function microtime(getAsFloat?: boolean): string | number
    +src/php/datetime/mktime.ts :: export function mktime( hour?: number | string, minute?: number | string, second?: number | string, month?: number | string, day?: number | string, year?: number | string, ): number | false
    +src/php/datetime/strftime.ts :: export function strftime(fmt: string, timestamp?: Date | number | string): string
    +src/php/datetime/strptime.ts :: export function strptime(dateStr: string, format: string): StrptimeResult | false
    +src/php/datetime/strtotime.ts :: export function strtotime(str: string, now?: number): false | number
    +src/php/datetime/time.ts :: export function time(): number
    +src/php/exec/escapeshellarg.ts :: export function escapeshellarg(arg: string): string
    +src/php/filesystem/basename.ts :: export function basename(path: string, suffix?: string): string
    +src/php/filesystem/dirname.ts :: export function dirname(path: string): string
    +src/php/filesystem/file_exists.ts :: export function file_exists(filename: string): boolean
    +src/php/filesystem/file_get_contents.ts :: export function file_get_contents( url: string, _flags?: FileGetContentsInput, _context?: FileGetContentsInput, _offset?: number, _maxLen?: number, ): string | false
    +src/php/filesystem/pathinfo.ts :: export function pathinfo(path: string, options?: PathInfoOptions): PathInfoMap | string | false
    +src/php/filesystem/realpath.ts :: export function realpath(path: string): string
    +src/php/funchand/call_user_func.ts :: export function call_user_func<TResult = FunctionArg, TArgs extends PhpCallableArgs = PhpCallableArgs>( cb: PhpCallableDescriptor<TArgs, TResult>, ...parameters: TArgs ): TResult
    +src/php/funchand/call_user_func_array.ts :: export function call_user_func_array<TResult = FunctionValue, TArgs extends PhpCallableArgs = PhpCallableArgs>( cb: PhpCallableDescriptor<TArgs, TResult>, parameters: [...TArgs], ): TResult
    +src/php/funchand/create_function.ts :: export function create_function(args: string, code: string): PhpCallable | false
    +src/php/funchand/function_exists.ts :: export function function_exists(funcName: string): boolean
    +src/php/funchand/get_defined_functions.ts :: export function get_defined_functions(): string[]
    +src/php/i18n/i18n_loc_get_default.ts :: export function i18n_loc_get_default(): string
    +src/php/i18n/i18n_loc_set_default.ts :: export function i18n_loc_set_default(name: string): boolean
    +src/php/info/assert_options.ts :: export function assert_options(what: string, _value?: AssertOptionValue): string | number | null
    +src/php/info/getenv.ts :: export function getenv(varname: string): string | false
    +src/php/info/ini_get.ts :: export function ini_get(varname: string): string
    +src/php/info/ini_set.ts :: export function ini_set(varname: string, newvalue: IniValue): IniValue | undefined
    +src/php/info/ini_set.ts :: export type IniValue = IniEntryValue | undefined
    +src/php/info/set_time_limit.ts :: export function set_time_limit(seconds: number): void
    +src/php/info/version_compare.ts :: export function version_compare(v1: string, v2: string, operator?: string): number | boolean | null
    +src/php/json/json_decode.ts :: export function json_decode<T = JsonValue>(strJson: string): T | null
    +src/php/json/json_encode.ts :: export function json_encode(mixedVal: JsonEncodeInput): string | null
    +src/php/json/json_last_error.ts :: export function json_last_error(): number
    +src/php/math/abs.ts :: export function abs(mixedNumber: string | number): number
    +src/php/math/acos.ts :: export function acos(arg: number): number
    +src/php/math/acosh.ts :: export function acosh(arg: number): number
    +src/php/math/asin.ts :: export function asin(arg: number): number
    +src/php/math/asinh.ts :: export function asinh(arg: number): number
    +src/php/math/atan.ts :: export function atan(arg: number): number
    +src/php/math/atan2.ts :: export function atan2(y: number, x: number): number
    +src/php/math/atanh.ts :: export function atanh(arg: number): number
    +src/php/math/base_convert.ts :: export function base_convert(number: string, frombase: number, tobase: number): string
    +src/php/math/bindec.ts :: export function bindec(binaryString: string): number
    +src/php/math/ceil.ts :: export function ceil(value: number): number
    +src/php/math/cos.ts :: export function cos(arg: number): number
    +src/php/math/cosh.ts :: export function cosh(arg: number): number
    +src/php/math/decbin.ts :: export function decbin(number: string | number): string
    +src/php/math/dechex.ts :: export function dechex(number: number): string
    +src/php/math/decoct.ts :: export function decoct(number: number): string
    +src/php/math/deg2rad.ts :: export function deg2rad(angle: number): number
    +src/php/math/exp.ts :: export function exp(arg: number): number
    +src/php/math/expm1.ts :: export function expm1(x: number): number
    +src/php/math/floor.ts :: export function floor(value: number): number
    +src/php/math/fmod.ts :: export function fmod(x: number | string, y: number | string): number
    +src/php/math/getrandmax.ts :: export function getrandmax(): number
    +src/php/math/hexdec.ts :: export function hexdec(hexString: string): number
    +src/php/math/hypot.ts :: export function hypot(x: HypotInput, y: HypotInput): number | null
    +src/php/math/is_finite.ts :: export function is_finite(val: NumberCheckValue): boolean
    +src/php/math/is_infinite.ts :: export function is_infinite(val: NumberCheckValue): boolean
    +src/php/math/is_nan.ts :: export function is_nan(val: NumberCheckValue): boolean
    +src/php/math/lcg_value.ts :: export function lcg_value(): number
    +src/php/math/log.ts :: export function log(arg: number, base: number): number
    +src/php/math/log10.ts :: export function log10(arg: number): number
    +src/php/math/log1p.ts :: export function log1p(x: number): number | string
    +src/php/math/max.ts :: export function max(...args: PhpMinMaxValue[]): PhpMinMaxValue
    +src/php/math/min.ts :: export function min(...args: PhpMinMaxValue[]): PhpMinMaxValue
    +src/php/math/mt_getrandmax.ts :: export function mt_getrandmax(): number
    +src/php/math/mt_rand.ts :: export function mt_rand(): number
    +src/php/math/mt_rand.ts :: export function mt_rand(...providedArgs: [min?: number | string, max?: number | string]): number
    +src/php/math/mt_rand.ts :: export function mt_rand(min: number | string, max: number | string): number
    +src/php/math/octdec.ts :: export function octdec(octString: string): number
    +src/php/math/pi.ts :: export function pi(): number
    +src/php/math/pow.ts :: export function pow(base: number, exp: number): number
    +src/php/math/rad2deg.ts :: export function rad2deg(angle: number): number
    +src/php/math/rand.ts :: export function rand(): number
    +src/php/math/rand.ts :: export function rand(...providedArgs: [min?: number, max?: number]): number
    +src/php/math/rand.ts :: export function rand(min: number, max: number): number
    +src/php/math/round.ts :: export function round(value: number, precision = 0, mode = 'PHP_ROUND_HALF_UP'): number
    +src/php/math/sin.ts :: export function sin(arg: number): number
    +src/php/math/sinh.ts :: export function sinh(arg: number): number
    +src/php/math/sqrt.ts :: export function sqrt(arg: number): number
    +src/php/math/tan.ts :: export function tan(arg: number): number
    +src/php/math/tanh.ts :: export function tanh(arg: number): number
    +src/php/misc/pack.ts :: export function pack(format: string, ...inputArgs: PackArgument[]): string
    +src/php/misc/uniqid.ts :: export function uniqid(prefix?: string, moreEntropy?: boolean): string
    +src/php/net-gopher/gopher_parsedir.ts :: export function gopher_parsedir(dirent: string): GopherKnownEntry | GopherUnknownEntry
    +src/php/network/inet_ntop.ts :: export function inet_ntop(a: string | number): string | false
    +src/php/network/inet_pton.ts :: export function inet_pton(a: string): string | false
    +src/php/network/ip2long.ts :: export function ip2long(argIP: string): number | false
    +src/php/network/long2ip.ts :: export function long2ip(ip: number): string | false
    +src/php/network/setcookie.ts :: export function setcookie( name: string, value: string, expires?: string | number | Date | null, path?: string | null, domain?: string | null, secure?: boolean, ): boolean
    +src/php/network/setrawcookie.ts :: export function setrawcookie( name: string, value: string, expires?: CookieExpires, path?: string | null, domain?: string | null, secure?: boolean, ): boolean
    +src/php/pcre/preg_match.ts :: export function preg_match(regex: string, str: string): boolean
    +src/php/pcre/preg_quote.ts :: export function preg_quote(str: string, delimiter: string): string
    +src/php/pcre/preg_replace.ts :: export function preg_replace(pattern: string, replacement: string, string: string): string
    +src/php/pcre/sql_regcase.ts :: export function sql_regcase(str: string): string
    +src/php/strings/addcslashes.ts :: export function addcslashes(str: string, charlist: string): string
    +src/php/strings/addslashes.ts :: export function addslashes(str: string): string
    +src/php/strings/bin2hex.ts :: export function bin2hex(s: string): string
    +src/php/strings/chop.ts :: export function chop(str: string, charlist: string): string
    +src/php/strings/chr.ts :: export function chr(codePt: number): string
    +src/php/strings/chunk_split.ts :: export function chunk_split(body: string, chunklen?: number | string, end?: string): string | false
    +src/php/strings/convert_cyr_string.ts :: export function convert_cyr_string(str: string, from: string, to: string): string
    +src/php/strings/convert_uuencode.ts :: export function convert_uuencode(str: UUEncodeInput): string | false
    +src/php/strings/count_chars.ts :: export function count_chars( str: string | number | boolean | null | undefined, mode = 0, ): { [key: string]: number } | string
    +src/php/strings/crc32.ts :: export function crc32(str: string): number
    +src/php/strings/echo.ts :: export function echo(...args: EchoValue[]): void
    +src/php/strings/explode.ts :: export function explode( ...args: [string | boolean | null | undefined, string | KeyedValues | (() => ExplodeValue) | undefined, number?] ): string[] | false | { 0: string } | null
    +src/php/strings/get_html_translation_table.ts :: export function get_html_translation_table( table: string | number = 'HTML_SPECIALCHARS', quoteStyle: string | number = 'ENT_COMPAT', ): Record<string, string>
    +src/php/strings/hex2bin.ts :: export function hex2bin(s: string | number): string | false
    +src/php/strings/html_entity_decode.ts :: export function html_entity_decode(string: string, quoteStyle?: string | number): string | false
    +src/php/strings/htmlentities.ts :: export function htmlentities( string: string, quoteStyle?: string | number, charset?: string, doubleEncode?: boolean | null, ): string
    +src/php/strings/htmlspecialchars.ts :: export function htmlspecialchars( string: string, quoteStyle?: HtmlSpecialCharsQuoteStyle, charset?: null, doubleEncode?: boolean, ): string
    +src/php/strings/htmlspecialchars_decode.ts :: export function htmlspecialchars_decode(string: string, quoteStyle?: HtmlSpecialCharsQuoteStyle): string
    +src/php/strings/implode.ts :: export function implode( ...providedArgs: [ glueOrPieces?: ImplodeValue[] | KeyedValues | string, pieces?: ImplodeValue[] | KeyedValues | string | undefined, ] ): string
    +src/php/strings/implode.ts :: export function implode(glue: string, pieces: ImplodeValue[] | KeyedValues | string | undefined): string
    +src/php/strings/implode.ts :: export function implode(pieces: ImplodeValue[] | KeyedValues | string | undefined): string
    +src/php/strings/join.ts :: export function join(glue: string, pieces: JoinValue[]): string
    +src/php/strings/lcfirst.ts :: export function lcfirst(str: string): string
    +src/php/strings/levenshtein.ts :: export function levenshtein(s1: string, s2: string, costIns?: number, costRep?: number, costDel?: number): number
    +src/php/strings/localeconv.ts :: export function localeconv(): LocaleValues
    +src/php/strings/ltrim.ts :: export function ltrim(str: string, charlist: string): string
    +src/php/strings/md5.ts :: export function md5(str: string): string
    +src/php/strings/md5_file.ts :: export function md5_file(str_filename: string): string | false
    +src/php/strings/metaphone.ts :: export function metaphone( word: MetaphoneValue, maxPhonemes?: string | number | boolean | null | undefined, ): string | false | null
    +src/php/strings/money_format.ts :: export function money_format(format: string, number: number): string | null
    +src/php/strings/nl2br.ts :: export function nl2br(str: string | null, isXhtml?: boolean): string
    +src/php/strings/nl_langinfo.ts :: export function nl_langinfo(item: string): string | string[] | false
    +src/php/strings/number_format.ts :: export function number_format( number: string | number, decimals: number | undefined, decPoint: string | undefined, thousandsSep: string | undefined, ): string
    +src/php/strings/ord.ts :: export function ord(string: string): number
    +src/php/strings/parse_str.ts :: export function parse_str(str: string, array?: ParseObject): void
    +src/php/strings/printf.ts :: export function printf(format: string, ...args: PrintfValue[]): number
    +src/php/strings/quoted_printable_decode.ts :: export function quoted_printable_decode(str: string): string
    +src/php/strings/quoted_printable_encode.ts :: export function quoted_printable_encode(str: string): string
    +src/php/strings/quotemeta.ts :: export function quotemeta(str: string): string
    +src/php/strings/rtrim.ts :: export function rtrim(str: string, charlist: string): string
    +src/php/strings/setlocale.ts :: export function setlocale(category: string, locale: LocaleInput): string | false
    +src/php/strings/sha1.ts :: export function sha1(str: string): string
    +src/php/strings/sha1_file.ts :: export function sha1_file(str_filename: string): string | false
    +src/php/strings/similar_text.ts :: export function similar_text( first: string | number | boolean | null | undefined, second: string | number | boolean | null | undefined, percent?: boolean | number | null | undefined, ): number
    +src/php/strings/soundex.ts :: export function soundex(str: SoundexValue): string
    +src/php/strings/split.ts :: export function split( delimiter: string | boolean | null | undefined, string: string | KeyedValues | (() => SplitValue) | undefined, ): string[] | false | { 0: string } | null
    +src/php/strings/sprintf.ts :: export function sprintf(format: string, ...args: SprintfValue[]): string | false
    +src/php/strings/sscanf.ts :: export function sscanf(str: string, format: string, ...refs: SscanfRef[]): SscanfValue[] | number
    +src/php/strings/str_getcsv.ts :: export function str_getcsv( input: string, delimiter?: string | null, enclosure?: string | null, escapeCharacter?: string | null, ): string[]
    +src/php/strings/str_ireplace.ts :: export function str_ireplace( search: string | string[], replace: string | string[], subject: string | string[], countObj?: CountObj, ): string | string[]
    +src/php/strings/str_pad.ts :: export function str_pad(input: string, padLength: number, padString: string, padType: string): string
    +src/php/strings/str_repeat.ts :: export function str_repeat(input: string, multiplier: number): string
    +src/php/strings/str_replace.ts :: export function str_replace( search: string | string[], replace: string | string[], subject: string | string[], countObj?: CountObj, ): string | string[]
    +src/php/strings/str_rot13.ts :: export function str_rot13(str: string | number): string
    +src/php/strings/str_shuffle.ts :: export function str_shuffle(...providedArgs: [input?: string | null | undefined]): string
    +src/php/strings/str_shuffle.ts :: export function str_shuffle(input: string | null | undefined): string
    +src/php/strings/str_split.ts :: export function str_split( string: string | number | boolean | null, splitLength?: number | string | null, ): string[] | false
    +src/php/strings/str_word_count.ts :: export function str_word_count( str: string, format?: 0 | 1 | 2, charlist?: string, ): number | string[] | { [key: number]: string }
    +src/php/strings/strcasecmp.ts :: export function strcasecmp(fString1: string, fString2: string): number
    +src/php/strings/strchr.ts :: export function strchr(haystack: string, needle: string, bool?: boolean): string | false
    +src/php/strings/strcmp.ts :: export function strcmp(str1: string, str2: string): number
    +src/php/strings/strcoll.ts :: export function strcoll(str1: string, str2: string): number
    +src/php/strings/strcspn.ts :: export function strcspn(str: string, mask: string, start?: number, length?: number): number
    +src/php/strings/strip_tags.ts :: export function strip_tags(input: string | number, allowed?: string): string
    +src/php/strings/stripos.ts :: export function stripos(fHaystack: string, fNeedle: string, fOffset?: number): number | false
    +src/php/strings/stripslashes.ts :: export function stripslashes(str: string): string
    +src/php/strings/stristr.ts :: export function stristr(haystack: string, needle: string, bool?: boolean): string | false
    +src/php/strings/strlen.ts :: export function strlen(string: string): number
    +src/php/strings/strnatcasecmp.ts :: export function strnatcasecmp(...providedArgs: [a?: string | number, b?: string | number]): number | null
    +src/php/strings/strnatcasecmp.ts :: export function strnatcasecmp(a: string | number, b: string | number): number
    +src/php/strings/strnatcmp.ts :: export function strnatcmp(left: NatCmpValue, right: NatCmpValue): number
    +src/php/strings/strncasecmp.ts :: export function strncasecmp(argStr1: string, argStr2: string, len: number): number
    +src/php/strings/strncmp.ts :: export function strncmp(str1: string, str2: string, lgth: number): number
    +src/php/strings/strpbrk.ts :: export function strpbrk(haystack: string, charList: string): string | false
    +src/php/strings/strpos.ts :: export function strpos(haystack: string, needle: string, offset: number): number | false
    +src/php/strings/strrchr.ts :: export function strrchr(haystack: string, needle: string): string | false
    +src/php/strings/strrev.ts :: export function strrev(string: string): string
    +src/php/strings/strripos.ts :: export function strripos(haystack: string, needle: string, offset?: number): number | false
    +src/php/strings/strrpos.ts :: export function strrpos(haystack: string, needle: string, offset?: boolean | number): number | false
    +src/php/strings/strspn.ts :: export function strspn(str1: string, str2: string, start?: number, lgth?: number): number
    +src/php/strings/strstr.ts :: export function strstr(haystack: string, needle: string, bool?: boolean): string | false
    +src/php/strings/strtok.ts :: export function strtok(str: string, tokens?: string): string | false
    +src/php/strings/strtolower.ts :: export function strtolower(str: string): string
    +src/php/strings/strtoupper.ts :: export function strtoupper(str: string): string
    +src/php/strings/strtr.ts :: export function strtr(str: string, trFrom: string | ReplacementMap | string[], trTo?: string | StrtrValue[]): string
    +src/php/strings/substr.ts :: export function substr(input: string | number, start: number, len?: number): string | false
    +src/php/strings/substr_compare.ts :: export function substr_compare( mainStr: string, str: string, offset: number, length: number, caseInsensitivity?: boolean, ): number | false
    +src/php/strings/substr_count.ts :: export function substr_count(haystack: string, needle: string, offset?: number, length?: number): number | false
    +src/php/strings/substr_replace.ts :: export function substr_replace(str: string, replace: string, start: number, length?: number): string
    +src/php/strings/trim.ts :: export function trim(str: string | number, charlist?: string | number): string
    +src/php/strings/ucfirst.ts :: export function ucfirst(str: string): string
    +src/php/strings/ucwords.ts :: export function ucwords(str: string): string
    +src/php/strings/vprintf.ts :: export function vprintf(format: string, ...args: PrintfValue[]): number
    +src/php/strings/vprintf.ts :: export function vprintf(format: string, ...restArgs: [PrintfValue[]] | PrintfValue[]): number
    +src/php/strings/vprintf.ts :: export function vprintf(format: string, args: PrintfValue[]): number
    +src/php/strings/vsprintf.ts :: export function vsprintf(format: string, args: VsprintfValue[]): string | false
    +src/php/strings/wordwrap.ts :: export function wordwrap(...rawArgs: [str: string, intWidth?: number, strBreak?: string, cut?: boolean]): string
    +src/php/url/base64_decode.ts :: export function base64_decode(encodedData: string | null | undefined): string | null | undefined
    +src/php/url/base64_encode.ts :: export function base64_encode(stringToEncode: string | null | undefined): string | null | undefined
    +src/php/url/http_build_query.ts :: export function http_build_query( formdata: QueryObject | QueryValue[], numericPrefix?: string | number, argSeparator?: string, encType?: HttpBuildQueryEncType, ): string
    +src/php/url/parse_url.ts :: export function parse_url(str: string, component?: string): ParseUrlResult | string
    +src/php/url/rawurldecode.ts :: export function rawurldecode(str: string): string
    +src/php/url/rawurlencode.ts :: export function rawurlencode(str: string): string
    +src/php/url/urldecode.ts :: export function urldecode(str: string): string
    +src/php/url/urlencode.ts :: export function urlencode(str: string): string
    +src/php/var/boolval.ts :: export function boolval(mixedVar: BoolValue): boolean
    +src/php/var/doubleval.ts :: export function doubleval(mixedVar: FloatvalInput): number
    +src/php/var/empty.ts :: export function empty(mixedVar: EmptyValue): boolean
    +src/php/var/floatval.ts :: export function floatval(mixedVar: FloatvalInput): number
    +src/php/var/floatval.ts :: export type FloatvalInput = NumericLike | boolean | PhpNullish
    +src/php/var/gettype.ts :: export function gettype(mixedVar: TypeInput): string
    +src/php/var/intval.ts :: export function intval(mixedVar: IntvalInput, base?: number): number
    +src/php/var/is_array.ts :: export function is_array(mixedVar: IsArrayValue): boolean
    +src/php/var/is_binary.ts :: export function is_binary(vr: string): boolean
    +src/php/var/is_bool.ts :: export function is_bool(mixedVar: boolean | number): boolean
    +src/php/var/is_buffer.ts :: export function is_buffer(vr: string): boolean
    +src/php/var/is_callable.ts :: export function is_callable(mixedVar: CallableValue, syntaxOnly?: boolean, callableName?: string): boolean
    +src/php/var/is_double.ts :: export function is_double(mixedVar: number): boolean
    +src/php/var/is_float.ts :: export function is_float(mixedVar: number): boolean
    +src/php/var/is_int.ts :: export function is_int(mixedVar: IntValue): mixedVar is number
    +src/php/var/is_integer.ts :: export function is_integer(mixedVar: number): boolean
    +src/php/var/is_long.ts :: export function is_long(mixedVar: number): boolean
    +src/php/var/is_null.ts :: export function is_null(mixedVar: string | null): boolean
    +src/php/var/is_numeric.ts :: export function is_numeric(mixedVar: NumericValue): mixedVar is number | string
    +src/php/var/is_object.ts :: export function is_object(mixedVar: ObjectValue): mixedVar is PhpAssoc<ObjectValue>
    +src/php/var/is_real.ts :: export function is_real(mixedVar: number): boolean
    +src/php/var/is_scalar.ts :: export function is_scalar(mixedVar: ScalarCheckValue): mixedVar is PhpScalar
    +src/php/var/is_string.ts :: export function is_string(mixedVar: string | number): boolean
    +src/php/var/is_unicode.ts :: export function is_unicode(vr: UnicodeValue): vr is string
    +src/php/var/isset.ts :: export function isset(...values: IssetValue[]): boolean
    +src/php/var/print_r.ts :: export function print_r(array: PrintValue, returnVal?: boolean): string | true
    +src/php/var/serialize.ts :: export function serialize(mixedValue: SerializeValue): string
    +src/php/var/strval.ts :: export function strval(str: StringValue): string
    +src/php/var/unserialize.ts :: export function unserialize(str: UnserializeInput, errorMode: ErrorMode = 'log'): UnserializedValue | false
    +src/php/var/var_dump.ts :: export function var_dump(...args: DumpValue[]): string
    +src/php/var/var_export.ts :: export function var_export( mixedExpression: VarExportInput, boolReturn?: boolean, idtLevel = 2, ): VarExportResult | null
    +src/php/var/var_export.ts :: export function var_export( mixedExpression: VarExportInput, boolReturn?: boolean, idtLevel?: number, ): VarExportResult | null
    +src/php/var/var_export.ts :: export function var_export(mixedExpression: VarExportInput, boolReturn: true, idtLevel?: number): VarExportResult
    +src/php/var/var_export.ts :: export function var_export(mixedExpression: VarExportInput, boolReturn?: false | undefined, idtLevel?: number): null
    +src/php/xdiff/xdiff_string_diff.ts :: export function xdiff_string_diff( oldData: string, newData: string, contextLines?: number, _minimal?: boolean, ): string | false
    +src/php/xdiff/xdiff_string_patch.ts :: export function xdiff_string_patch( originalStr: string, patch: string, flags?: number | string | string[], errorObj?: PatchErrorObject, ): string | false
    +src/php/xml/utf8_decode.ts :: export function utf8_decode(strData: string | number | boolean | null | undefined): string
    +src/php/xml/utf8_encode.ts :: export function utf8_encode(argString: string | null | undefined): string
    
  • docs/typescript.md+2378 0 added
  • .github/workflows/ci.yml+10 1 modified
    @@ -20,7 +20,7 @@ jobs:
               registry-url: 'https://registry.npmjs.org'
           - name: Engine bump guardrail
             run: |
    -          node scripts/check-engine-bump.js
    +          node scripts/check-engine-bump.ts
           - name: Get yarn cache directory path
             id: yarn-cache-dir-path
             run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
    @@ -45,6 +45,15 @@ jobs:
           - name: TypeScript Check
             run: |
               corepack yarn lint:ts
    +      - name: TypeScript Strict-Next Check
    +        run: |
    +          corepack yarn lint:ts:strict-next
    +      - name: Type Debt Policy Check
    +        run: |
    +          corepack yarn lint:ts:debt:policy
    +      - name: API Signature Snapshot Check
    +        run: |
    +          corepack yarn lint:api:snapshot
           - name: Header Format Check
             run: |
               corepack yarn lint:headers
    
  • package.json+26 14 modified
    @@ -19,6 +19,7 @@
         "type": "git",
         "url": "https://github.com/locutusjs/locutus.git"
       },
    +  "type": "module",
       "license": "MIT",
       "author": "Kevin van Zonneveld <kevin@vanzonneveld.net>",
       "browser": {
    @@ -28,27 +29,39 @@
       "scripts": {
         "browser:bundle": "browserify test/browser/app.js --outfile test/browser/bundle.js",
         "browser:watch": "budo test/browser/app.js --live --serve test/browser/bundle.js",
    -    "build:dist": "rimraf dist && cp -r src dist && rm -rf dist/_util && cp package.json README.md dist/",
    -    "build:indices": "node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON src/_util/cli.ts reindex",
    -    "build:tests:noskip": "rimraf test/generated && node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON src/_util/cli.ts writetests --noskip",
    -    "build:tests": "rimraf test/generated && node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON src/_util/cli.ts writetests",
    +    "build:dist": "rimraf dist && cp -r src dist && cp package.json README.md dist && tsc -p tsconfig.build.json --outDir dist && tsc -p tsconfig.build.esm.json --outDir dist/esm && find dist -name '*.ts' ! -name '*.d.ts' -delete && node scripts/fix-cjs-exports.ts dist && rm -rf dist/_util dist/esm/_util",
    +    "build:indices": "node src/_util/cli.ts reindex",
    +    "build:tests:noskip": "rimraf test/generated && node src/_util/cli.ts writetests --noskip",
    +    "build:tests": "rimraf test/generated && node src/_util/cli.ts writetests",
         "build": "npm-run-all 'build:*'",
    -    "injectweb": "rimraf website/source/{c,golang,php,python,ruby} && node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON src/_util/cli.ts injectweb",
    -    "check": "npm-run-all --serial fix:biome lint lint:ts lint:headers lint:no-stray-js test:languages",
    +    "injectweb": "rimraf website/source/{c,golang,php,python,ruby} && node src/_util/cli.ts injectweb",
    +    "check": "npm-run-all --serial fix:biome lint lint:ts lint:ts:strict-next lint:ts:debt:policy lint:api:snapshot lint:type:contracts lint:headers lint:no-stray-js test:languages",
         "fix:biome": "biome check --write .",
    -    "fix:headers": "node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON src/_util/formatHeaders.ts fix",
    -    "lint:headers": "node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON src/_util/formatHeaders.ts check",
    -    "lint:no-stray-js": "bash -c 'found=$(find src test -name \"*.js\" | grep -vE \"^src/(awk|c|clojure|elixir|golang|julia|lua|perl|php|python|r|ruby)/|/index\\.js$|^test/browser/\"); if [ -n \"$found\" ]; then echo \"Stray .js files found (should be .ts):\"; echo \"$found\"; exit 1; fi'",
    +    "fix:headers": "node src/_util/formatHeaders.ts fix",
    +    "lint:headers": "node src/_util/formatHeaders.ts check",
    +    "compare:dt:signatures": "node scripts/compare-definitelytyped-signatures.ts",
    +    "lint:no-stray-js": "node scripts/check-allowed-js-files.ts",
         "lint:ts": "tsc --noEmit -p tsconfig.json",
    +    "lint:ts:strict-next": "tsc --noEmit -p tsconfig.strict-next.json",
    +    "lint:ts:debt:policy": "node scripts/check-ts-debt-policy.ts",
    +    "lint:api:snapshot:php": "node scripts/check-api-signature-snapshot.ts --scope=php",
    +    "lint:api:snapshot:nonphp": "node scripts/check-api-signature-snapshot.ts --scope=non-php",
    +    "lint:api:snapshot": "npm-run-all --serial lint:api:snapshot:php lint:api:snapshot:nonphp",
    +    "lint:type:contracts": "node scripts/check-type-contracts-snapshot.ts",
    +    "fix:api:snapshot:php": "node scripts/check-api-signature-snapshot.ts --scope=php --update",
    +    "fix:api:snapshot:nonphp": "node scripts/check-api-signature-snapshot.ts --scope=non-php --update",
    +    "fix:api:snapshot": "npm-run-all --serial fix:api:snapshot:php fix:api:snapshot:nonphp",
    +    "fix:type:contracts": "node scripts/check-type-contracts-snapshot.ts --update",
         "fix:markdown": "remark {README,CONTRIBUTING}.md --output",
         "fix": "npm-run-all --serial 'fix:**'",
         "lint": "biome check .",
         "playground:start": "cd test/browser && node server.js",
         "test:languages:noskip": "yarn build:tests:noskip && vitest run",
         "test:languages": "yarn build:tests && vitest run",
    -    "test:module": "node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON test/module/module.ts",
    -    "test:parity": "node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON test/parity/index.ts",
    -    "test:parity:php": "node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON test/parity/index.ts php",
    +    "test:module:smoke": "node test/module/module.ts",
    +    "test:module": "npm-run-all --serial build:dist test:module:smoke",
    +    "test:parity": "node test/parity/index.ts",
    +    "test:parity:php": "node test/parity/index.ts php",
         "test:util": "vitest run test/util/",
         "test": "npm-run-all test:languages test:module",
         "website:install": "cd website && yarn",
    @@ -64,7 +77,6 @@
         "budo": "11.8.4",
         "cross-env": "10",
         "debug": "^4.4.3",
    -    "esprima": "4.0.1",
         "globby": "4.1.0",
         "indent-string": "2.1.0",
         "js-yaml": "4.1.1",
    @@ -81,7 +93,7 @@
       },
       "packageManager": "yarn@4.12.0",
       "engines": {
    -    "node": ">= 10",
    +    "node": ">= 22",
         "yarn": ">= 1"
       },
       "readmeFilename": "README.md"
    
  • README.md+12 45 modified
    @@ -5,66 +5,33 @@
     [![Verified: PHP 8.3](https://img.shields.io/badge/verified-PHP%208.3-777BB4.svg)](https://locutus.io/php/)
     [![Verified: Python 3.12](https://img.shields.io/badge/verified-Python%203.12-3776AB.svg)](https://locutus.io/python/)
     
    -> All your standard libraries will be assimilated into our JavaScript collective. Resistance is futile.
    +> All your standard libraries will be assimilated into our ~~JavaScript~~ TypeScript collective. Resistance is futile.
     
    -Welcome to Locutus, where the boundaries of coding languages blur. We're a dedicated collective developers on a mission
    -to explore the possibilities of porting standard libraries from various programming language (Go, Ruby, PHP, C) to
    -JavaScript. Our journey is one of discovery, innovation, and sometimes, delightful chaos.
    +Locutus is ~500 TypeScript implementations of standard library functions from PHP, Go, Python, Ruby, C, and [more](https://locutus.io/). Each function is individually importable and tree-shakeable.
     
    -From the complex to the quirky, we assimilate libraries with a spirit of curiosity and a penchant for experimentation.
    -Our creations typically start as rainy Sunday afternoon puzzles, and end up ranging from groundbreaking functions that
    -enhance the JavaScript ecosystem, to unique oddities that challenge the norms of coding.
    -
    -As we navigate through this uncharted territory, we invite you to join us. Whether to contribute, learn, or simply
    -marvel at the wonders of cross-language integration and portability, your presence on GitHub is valued.
    -
    -Embark on this journey with us at [locutus.io](https://locutus.io/).
    -
    -Use our creations at your own risk, and may they inspire you to push the boundaries of what's possible with JavaScript.
    -
    -## Table of contents
    -
    -- [Install](#install)
    -- [Use](#use)
    -- [Development](#development)
    -- [License](#license)
    +Most of these started as rainy Sunday afternoon puzzles. Some are genuinely useful. Some are just fun to write. All of them are a way to learn how different languages solve the same problems.
     
     ## Install
     
     ```bash
    -yarn add locutus
    +npm install locutus
     ```
     
     ## Use
     
    -```bash
    -$ vim php.js
    -```
    +```typescript
    +import { sprintf } from 'locutus/php/strings/sprintf'
     
    -```javascript
    -const sprintf = require('locutus/php/strings/sprintf')
    -const echo = require('locutus/php/strings/echo')
     const effectiveness = 'futile'
    -echo(sprintf('Resistance is %s', effectiveness))
    +console.log(sprintf('Resistance is %s', effectiveness))
    +// Resistance is futile
     ```
     
    -```bash
    -$ node php.js
    -Resistance is futile
    -```
    -
    -```bash
    -$ vim go.js
    -```
    +```typescript
    +import { Contains } from 'locutus/golang/strings/Contains'
     
    -```javascript
    -const strings = require('locutus/golang/strings')
    -console.log(strings.Contains('Locutus', 'cut'))
    -```
    -
    -```bash
    -$ node go.js
    -true
    +console.log(Contains('Locutus', 'cut'))
    +// true
     ```
     
     ## Development
    
  • scripts/check-allowed-js-files.ts+40 0 added
    @@ -0,0 +1,40 @@
    +import { execFileSync } from 'node:child_process'
    +import fs from 'node:fs'
    +
    +const allowedJsFiles = new Set<string>([
    +  '.remarkrc.mjs',
    +  'test/browser/app.js',
    +  'website/themes/icarus/scripts/fancybox.js',
    +  'website/themes/icarus/scripts/locutus_stats.js',
    +  'website/themes/icarus/scripts/meta.js',
    +  'website/themes/icarus/scripts/thumbnail.js',
    +  'website/themes/icarus/source/js/insight.js',
    +  'website/themes/icarus/source/js/locutus.js',
    +  'website/themes/icarus/source/js/main.js',
    +  'website/themes/icarus/source/vendor/fancybox/helpers/jquery.fancybox-buttons.js',
    +  'website/themes/icarus/source/vendor/fancybox/helpers/jquery.fancybox-media.js',
    +  'website/themes/icarus/source/vendor/fancybox/helpers/jquery.fancybox-thumbs.js',
    +  'website/themes/icarus/source/vendor/fancybox/jquery.fancybox.js',
    +  'website/themes/icarus/source/vendor/fancybox/jquery.fancybox.pack.js',
    +  'website/themes/icarus/source/vendor/jquery/2.1.3/jquery.min.js',
    +])
    +
    +const output = execFileSync('git', ['ls-files', '*.js', '*.mjs'], { encoding: 'utf8' }).trim()
    +const trackedJsFiles = output
    +  .split('\n')
    +  .map((filePath) => filePath.trim())
    +  .filter((filePath) => filePath.length > 0)
    +  .filter((filePath) => fs.existsSync(filePath))
    +  .sort((left, right) => left.localeCompare(right))
    +
    +const unexpectedFiles = trackedJsFiles.filter((filePath) => !allowedJsFiles.has(filePath))
    +
    +if (unexpectedFiles.length > 0) {
    +  console.error('Unexpected .js/.mjs files detected. Port these to TypeScript or update allowlist intentionally:')
    +  for (const filePath of unexpectedFiles) {
    +    console.error(`  - ${filePath}`)
    +  }
    +  process.exit(1)
    +}
    +
    +console.log(`js policy ok: ${trackedJsFiles.length} allowed .js/.mjs file(s)`)
    
  • scripts/check-api-signature-snapshot.ts+102 0 added
    @@ -0,0 +1,102 @@
    +import fs from 'node:fs'
    +import path from 'node:path'
    +import ts from 'typescript'
    +
    +const cwd = process.cwd()
    +const shouldUpdate = process.argv.includes('--update')
    +const scopeArg = process.argv.find((arg) => arg.startsWith('--scope='))
    +const scope = scopeArg?.slice('--scope='.length) ?? 'php'
    +
    +if (scope !== 'php' && scope !== 'non-php') {
    +  console.error(`Invalid --scope value: ${scope}`)
    +  console.error("Allowed values: 'php', 'non-php'")
    +  process.exit(1)
    +}
    +
    +const isPhpScope = scope === 'php'
    +const snapshotPath = path.join(
    +  cwd,
    +  'docs',
    +  isPhpScope ? 'php-api-signatures.snapshot' : 'non-php-api-signatures.snapshot',
    +)
    +const srcRootDir = path.join(cwd, 'src')
    +const srcPhpDir = path.join(srcRootDir, 'php')
    +const label = isPhpScope ? 'PHP' : 'Non-PHP'
    +
    +const hasExportModifier = (node: ts.Node): boolean =>
    +  !!node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
    +
    +const normalizeWhitespace = (value: string): string => value.replace(/\s+/g, ' ').trim()
    +
    +const getFunctionSignatureText = (sourceFile: ts.SourceFile, declaration: ts.FunctionDeclaration): string => {
    +  const sourceText = sourceFile.getFullText()
    +  const start = declaration.getStart(sourceFile)
    +  const end = declaration.body ? declaration.body.getStart(sourceFile) : declaration.end
    +  return normalizeWhitespace(sourceText.slice(start, end))
    +}
    +
    +const sourceFiles = ts.sys
    +  .readDirectory(isPhpScope ? srcPhpDir : srcRootDir, ['.ts'], undefined, undefined)
    +  .filter((filePath) => !filePath.endsWith('.d.ts'))
    +  .filter((filePath) => (isPhpScope ? true : !filePath.includes(`${path.sep}src${path.sep}php${path.sep}`)))
    +
    +const lines = new Set<string>()
    +
    +for (const filePath of sourceFiles) {
    +  const sourceText = fs.readFileSync(filePath, 'utf8')
    +  const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
    +  const relativePath = path.relative(cwd, filePath).replaceAll(path.sep, '/')
    +
    +  sourceFile.forEachChild((node) => {
    +    if (ts.isFunctionDeclaration(node) && hasExportModifier(node) && node.name) {
    +      const signature = getFunctionSignatureText(sourceFile, node)
    +      lines.add(`${relativePath} :: ${signature}`)
    +      return
    +    }
    +
    +    if (ts.isTypeAliasDeclaration(node) && hasExportModifier(node)) {
    +      lines.add(`${relativePath} :: ${normalizeWhitespace(node.getText(sourceFile))}`)
    +      return
    +    }
    +
    +    if (ts.isInterfaceDeclaration(node) && hasExportModifier(node)) {
    +      lines.add(`${relativePath} :: ${normalizeWhitespace(node.getText(sourceFile))}`)
    +    }
    +  })
    +}
    +
    +const snapshotContent = [...lines].sort().join('\n') + '\n'
    +
    +if (shouldUpdate) {
    +  fs.writeFileSync(snapshotPath, snapshotContent)
    +  console.log(`Updated API signature snapshot at ${path.relative(cwd, snapshotPath)}`)
    +  process.exit(0)
    +}
    +
    +if (!fs.existsSync(snapshotPath)) {
    +  console.error(`Snapshot file missing: ${path.relative(cwd, snapshotPath)}`)
    +  console.error('Run: corepack yarn fix:api:snapshot')
    +  process.exit(1)
    +}
    +
    +const expected = fs.readFileSync(snapshotPath, 'utf8')
    +if (expected !== snapshotContent) {
    +  const expectedLines = expected.split('\n')
    +  const currentLines = snapshotContent.split('\n')
    +  let firstDiffIndex = 0
    +  while (
    +    firstDiffIndex < expectedLines.length &&
    +    firstDiffIndex < currentLines.length &&
    +    expectedLines[firstDiffIndex] === currentLines[firstDiffIndex]
    +  ) {
    +    firstDiffIndex += 1
    +  }
    +
    +  console.error(`${label} API signature snapshot mismatch at line ${firstDiffIndex + 1}`)
    +  console.error(`Expected: ${expectedLines[firstDiffIndex] ?? '<EOF>'}`)
    +  console.error(`Current:  ${currentLines[firstDiffIndex] ?? '<EOF>'}`)
    +  console.error('If this change is intentional, run: corepack yarn fix:api:snapshot')
    +  process.exit(1)
    +}
    +
    +console.log(`${label} API signature snapshot is up to date (${path.relative(cwd, snapshotPath)})`)
    
  • scripts/check-engine-bump.js+0 82 removed
    @@ -1,82 +0,0 @@
    -#!/usr/bin/env node
    -
    -const { execSync } = require('node:child_process')
    -const { readFileSync } = require('node:fs')
    -
    -function parseSemver(input) {
    -  if (!input) {
    -    return null
    -  }
    -
    -  const match = String(input).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/)
    -  if (!match) {
    -    return null
    -  }
    -
    -  return [Number(match[1]), Number(match[2] || 0), Number(match[3] || 0)]
    -}
    -
    -function compareSemver(a, b) {
    -  for (let i = 0; i < 3; i += 1) {
    -    if (a[i] > b[i]) {
    -      return 1
    -    }
    -    if (a[i] < b[i]) {
    -      return -1
    -    }
    -  }
    -  return 0
    -}
    -
    -function readJson(ref) {
    -  if (!ref) {
    -    return JSON.parse(readFileSync('package.json', 'utf8'))
    -  }
    -
    -  const content = execSync(`git show ${ref}:package.json`, { encoding: 'utf8' })
    -  return JSON.parse(content)
    -}
    -
    -const baseRef = process.env.GITHUB_BASE_REF
    -if (!baseRef) {
    -  console.log('[engine-bump] GITHUB_BASE_REF not set; skipping check.')
    -  process.exit(0)
    -}
    -
    -const basePkg = readJson(`origin/${baseRef}`)
    -const headPkg = readJson(null)
    -
    -const baseEngine = basePkg.engines && basePkg.engines.node
    -const headEngine = headPkg.engines && headPkg.engines.node
    -
    -if (baseEngine === headEngine) {
    -  process.exit(0)
    -}
    -
    -if (!headEngine) {
    -  console.log('[engine-bump] engines.node removed or unset; skipping check.')
    -  process.exit(0)
    -}
    -
    -const baseMin = parseSemver(baseEngine)
    -const headMin = parseSemver(headEngine)
    -
    -let raised = true
    -if (baseMin && headMin) {
    -  raised = compareSemver(headMin, baseMin) > 0
    -}
    -
    -if (!raised) {
    -  process.exit(0)
    -}
    -
    -const baseVersion = parseSemver(basePkg.version) || [0, 0, 0]
    -const headVersion = parseSemver(headPkg.version) || [0, 0, 0]
    -
    -if (headVersion[0] <= baseVersion[0]) {
    -  console.error(
    -    `[engine-bump] engines.node increased from "${baseEngine}" to "${headEngine}" without a major version bump (${basePkg.version} -> ${headPkg.version}).`,
    -  )
    -  console.error('Engine bumps are treated as breaking changes. Bump the major version or revert engines.node.')
    -  process.exit(1)
    -}
    
  • scripts/check-engine-bump.ts+137 0 added
    @@ -0,0 +1,137 @@
    +import { execSync } from 'node:child_process'
    +import { readdirSync, readFileSync } from 'node:fs'
    +import path from 'node:path'
    +
    +type Semver = [number, number, number]
    +
    +interface PackageJsonShape {
    +  version?: string
    +  engines?: {
    +    node?: string
    +  }
    +}
    +
    +const parseSemver = (input: unknown): Semver | null => {
    +  if (!input) {
    +    return null
    +  }
    +
    +  const match = String(input).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/)
    +  if (!match) {
    +    return null
    +  }
    +
    +  return [Number(match[1]), Number(match[2] || 0), Number(match[3] || 0)]
    +}
    +
    +const compareSemver = (left: Semver, right: Semver): number => {
    +  for (let i = 0; i < 3; i += 1) {
    +    if (left[i] > right[i]) {
    +      return 1
    +    }
    +    if (left[i] < right[i]) {
    +      return -1
    +    }
    +  }
    +  return 0
    +}
    +
    +const readJson = (ref?: string): PackageJsonShape => {
    +  if (!ref) {
    +    return JSON.parse(readFileSync('package.json', 'utf8')) as PackageJsonShape
    +  }
    +
    +  const content = execSync(`git show ${ref}:package.json`, { encoding: 'utf8' })
    +  return JSON.parse(content) as PackageJsonShape
    +}
    +
    +const escapeRegex = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
    +
    +const hasPendingMajorChangeset = (packageName: string): boolean => {
    +  const changesetDir = '.changeset'
    +  let entries: ReturnType<typeof readdirSync>
    +
    +  try {
    +    entries = readdirSync(changesetDir, { withFileTypes: true })
    +  } catch {
    +    return false
    +  }
    +
    +  const markdownFiles = entries
    +    .filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
    +    .map((entry) => path.join(changesetDir, entry.name))
    +
    +  for (const filePath of markdownFiles) {
    +    let content = ''
    +    try {
    +      content = readFileSync(filePath, 'utf8')
    +    } catch {
    +      continue
    +    }
    +
    +    const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/)
    +    if (!frontmatterMatch) {
    +      continue
    +    }
    +
    +    const frontmatter = frontmatterMatch[1]
    +    const packageRegex = new RegExp(
    +      String.raw`["']?${escapeRegex(packageName)}["']?\s*:\s*["']?(major|premajor)["']?\b`,
    +      'i',
    +    )
    +    if (packageRegex.test(frontmatter)) {
    +      return true
    +    }
    +  }
    +
    +  return false
    +}
    +
    +const baseRef = process.env.GITHUB_BASE_REF
    +if (!baseRef) {
    +  console.log('[engine-bump] GITHUB_BASE_REF not set; skipping check.')
    +  process.exit(0)
    +}
    +
    +const basePkg = readJson(`origin/${baseRef}`)
    +const headPkg = readJson()
    +
    +const baseEngine = basePkg.engines?.node
    +const headEngine = headPkg.engines?.node
    +
    +if (baseEngine === headEngine) {
    +  process.exit(0)
    +}
    +
    +if (!headEngine) {
    +  console.log('[engine-bump] engines.node removed or unset; skipping check.')
    +  process.exit(0)
    +}
    +
    +const baseMin = parseSemver(baseEngine)
    +const headMin = parseSemver(headEngine)
    +
    +let raised = true
    +if (baseMin && headMin) {
    +  raised = compareSemver(headMin, baseMin) > 0
    +}
    +
    +if (!raised) {
    +  process.exit(0)
    +}
    +
    +const baseVersion = parseSemver(basePkg.version) || [0, 0, 0]
    +const headVersion = parseSemver(headPkg.version) || [0, 0, 0]
    +
    +if (headVersion[0] <= baseVersion[0]) {
    +  if (hasPendingMajorChangeset('locutus')) {
    +    console.log('[engine-bump] engines.node increased with pending major changeset for "locutus"; allowing.')
    +    process.exit(0)
    +  }
    +
    +  console.error(
    +    `[engine-bump] engines.node increased from "${baseEngine}" to "${headEngine}" without a major version bump (${basePkg.version} -> ${headPkg.version}).`,
    +  )
    +  console.error('Engine bumps are treated as breaking changes. Bump the major version or revert engines.node.')
    +  process.exit(1)
    +}
    
  • scripts/check-ts-debt-policy.ts+1301 0 added
    @@ -0,0 +1,1301 @@
    +import fs from 'node:fs'
    +import path from 'node:path'
    +import ts from 'typescript'
    +
    +interface Finding {
    +  file: string
    +  count: number
    +}
    +
    +const MAX_SRC_PHP_RAW_INDEX_SIGNATURE_UNKNOWN = 0
    +const MAX_SRC_PHP_EXPORTED_UNKNOWN_RETURN_TYPES = 0
    +const MAX_SRC_PHP_EXPORTED_OBJECT_KEYWORD = 0
    +const MAX_SRC_PHP_EXPORTED_EMPTY_OBJECT_TYPE = 0
    +const MAX_SRC_PHP_EXPORTED_FUNCTION_WITHOUT_RETURN_TYPE = 0
    +const MAX_SRC_NON_PHP_EXPORTED_FUNCTION_WITHOUT_RETURN_TYPE = 0
    +const MAX_SRC_NON_PHP_LOCAL_NULLISH_ALIAS = 0
    +const MAX_SRC_NON_PHP_LOCAL_OBJECT_ALIAS = 0
    +const MAX_SRC_NON_PHP_LOCAL_UNKNOWN_ALIAS = 0
    +const MAX_SRC_PHP_EXPORTED_PHPVALUE_IDENTIFIER = 0
    +const MAX_SRC_PHP_EXPORTED_PHPINPUT_OUTSIDE_HELPERS = 0
    +const MAX_SRC_PHP_ARRAY_EXPORTED_PHPVALUE_IDENTIFIER = 0
    +const MAX_SRC_PHP_ARRAY_EXPORTED_SORTFLAG_STRING_PARAM = 0
    +const MAX_SRC_PHP_ARRAY_EXPORTED_MODE_CASE_STRING_PARAM = 0
    +const MAX_SRC_PHP_UNKNOWN_KEYWORD = 0
    +const MAX_SRC_PHP_ARRAY_UNKNOWN_KEYWORD = 0
    +const MAX_SRC_PHP_VAR_UNKNOWN_KEYWORD = 0
    +const MAX_SRC_PHP_STRINGS_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_CTYPE_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_INFO_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_HELPERS_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_URL_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_FUNCHAND_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_JSON_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_DATETIME_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_ARRAY_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_BC_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_FILESYSTEM_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_MISC_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_PCRE_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_VAR_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_XDIFF_AS_EXPRESSION = 0
    +const MAX_SRC_PHP_LOCAL_PHPVALUE_ALIAS = 0
    +const MAX_SRC_PHP_LOCAL_PHPINPUT_ALIAS_OUTSIDE_HELPERS = 0
    +const MAX_SRC_PHP_DIRECT_INI_GLOBAL_READS = 0
    +const MAX_SRC_PHP_PHPVALUE_IDENTIFIER = 0
    +const MAX_SRC_PHP_INLINE_NULLISH_ALIAS = 0
    +const MAX_SRC_PHP_REFLECT_GET_SET = 0
    +const MAX_SRC_PHP_GLOBALTHIS_IDENTIFIER = 0
    +const MAX_SRC_PHP_PHPMIXED_KEYWORD = 0
    +const MAX_SRC_PHP_ARRAY_PHPMIXED_KEYWORD = 0
    +const MAX_SRC_PHP_VAR_PHPMIXED_KEYWORD = 0
    +const MAX_SRC_PHP_STRINGS_PHPMIXED_KEYWORD = 0
    +
    +const cwd = process.cwd()
    +const srcDir = path.join(cwd, 'src')
    +
    +const sourceFiles = ts.sys
    +  .readDirectory(srcDir, ['.ts'], undefined, undefined)
    +  .filter((filePath) => !filePath.endsWith('.d.ts'))
    +
    +const tsNoCheckFiles: string[] = []
    +const tsIgnoreFindings: Finding[] = []
    +const tsExpectErrorFindings: Finding[] = []
    +const recordStringUnknownFindings: Finding[] = []
    +const functionTypeFindings: Finding[] = []
    +const asUnknownAsFindings: Finding[] = []
    +const argumentsIdentifierFindings: Finding[] = []
    +const exportedUnknownReturnTypeFindings: Finding[] = []
    +const exportedObjectKeywordFindings: Finding[] = []
    +const exportedEmptyObjectTypeFindings: Finding[] = []
    +const exportedFunctionWithoutReturnTypeFindings: Finding[] = []
    +const nonPhpExportedFunctionWithoutReturnTypeFindings: Finding[] = []
    +const nonPhpLocalNullishAliasFindings: Finding[] = []
    +const nonPhpLocalObjectAliasFindings: Finding[] = []
    +const nonPhpLocalUnknownAliasFindings: Finding[] = []
    +const exportedPhpValueIdentifierFindings: Finding[] = []
    +const exportedPhpInputOutsideHelpersFindings: Finding[] = []
    +const arrayExportedPhpValueIdentifierFindings: Finding[] = []
    +const arrayExportedSortFlagStringParamFindings: Finding[] = []
    +const arrayExportedModeCaseStringParamFindings: Finding[] = []
    +const localPhpValueAliasFindings: Finding[] = []
    +const localPhpInputAliasOutsideHelpersFindings: Finding[] = []
    +const directIniGlobalReadFindings: Finding[] = []
    +const stringsAsExpressionFindings: Finding[] = []
    +const ctypeAsExpressionFindings: Finding[] = []
    +const infoAsExpressionFindings: Finding[] = []
    +const helpersAsExpressionFindings: Finding[] = []
    +const urlAsExpressionFindings: Finding[] = []
    +const funchandAsExpressionFindings: Finding[] = []
    +const jsonAsExpressionFindings: Finding[] = []
    +const datetimeAsExpressionFindings: Finding[] = []
    +const arrayAsExpressionFindings: Finding[] = []
    +const bcAsExpressionFindings: Finding[] = []
    +const filesystemAsExpressionFindings: Finding[] = []
    +const miscAsExpressionFindings: Finding[] = []
    +const pcreAsExpressionFindings: Finding[] = []
    +const varAsExpressionFindings: Finding[] = []
    +const xdiffAsExpressionFindings: Finding[] = []
    +const phpMixedFindings: Finding[] = []
    +const arrayPhpMixedFindings: Finding[] = []
    +const varPhpMixedFindings: Finding[] = []
    +const stringsPhpMixedFindings: Finding[] = []
    +const phpValueIdentifierFindings: Finding[] = []
    +const inlineNullishAliasFindings: Finding[] = []
    +const reflectGetSetFindings: Finding[] = []
    +const globalThisIdentifierFindings: Finding[] = []
    +let srcPhpRawIndexSignatureUnknownCount = 0
    +let srcPhpExportedUnknownReturnTypeCount = 0
    +let srcPhpExportedObjectKeywordCount = 0
    +let srcPhpExportedEmptyObjectTypeCount = 0
    +let srcPhpExportedFunctionWithoutReturnTypeCount = 0
    +let srcNonPhpExportedFunctionWithoutReturnTypeCount = 0
    +let srcNonPhpLocalNullishAliasCount = 0
    +let srcNonPhpLocalObjectAliasCount = 0
    +let srcNonPhpLocalUnknownAliasCount = 0
    +let srcPhpExportedPhpValueIdentifierCount = 0
    +let srcPhpExportedPhpInputOutsideHelpersCount = 0
    +let srcPhpArrayExportedPhpValueIdentifierCount = 0
    +let srcPhpArrayExportedSortFlagStringParamCount = 0
    +let srcPhpArrayExportedModeCaseStringParamCount = 0
    +let srcPhpUnknownKeywordCount = 0
    +let srcPhpArrayUnknownKeywordCount = 0
    +let srcPhpVarUnknownKeywordCount = 0
    +let srcPhpStringsAsExpressionCount = 0
    +let srcPhpCtypeAsExpressionCount = 0
    +let srcPhpInfoAsExpressionCount = 0
    +let srcPhpHelpersAsExpressionCount = 0
    +let srcPhpUrlAsExpressionCount = 0
    +let srcPhpFunchandAsExpressionCount = 0
    +let srcPhpJsonAsExpressionCount = 0
    +let srcPhpDatetimeAsExpressionCount = 0
    +let srcPhpArrayAsExpressionCount = 0
    +let srcPhpBcAsExpressionCount = 0
    +let srcPhpFilesystemAsExpressionCount = 0
    +let srcPhpMiscAsExpressionCount = 0
    +let srcPhpPcreAsExpressionCount = 0
    +let srcPhpVarAsExpressionCount = 0
    +let srcPhpXdiffAsExpressionCount = 0
    +let srcPhpLocalPhpValueAliasCount = 0
    +let srcPhpLocalPhpInputAliasOutsideHelpersCount = 0
    +let srcPhpDirectIniGlobalReadCount = 0
    +let srcPhpPhpValueIdentifierCount = 0
    +let srcPhpInlineNullishAliasCount = 0
    +let srcPhpReflectGetSetCount = 0
    +let srcPhpGlobalThisIdentifierCount = 0
    +let srcPhpPhpMixedKeywordCount = 0
    +let srcPhpArrayPhpMixedKeywordCount = 0
    +let srcPhpVarPhpMixedKeywordCount = 0
    +let srcPhpStringsPhpMixedKeywordCount = 0
    +
    +const countFunctionTypeReferences = (sourceFile: ts.SourceFile): number => {
    +  let count = 0
    +
    +  const visit = (node: ts.Node): void => {
    +    if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && node.typeName.text === 'Function') {
    +      count += 1
    +    }
    +    ts.forEachChild(node, visit)
    +  }
    +
    +  visit(sourceFile)
    +  return count
    +}
    +
    +const countArgumentsIdentifiers = (sourceFile: ts.SourceFile): number => {
    +  let count = 0
    +
    +  const visit = (node: ts.Node): void => {
    +    if (ts.isIdentifier(node) && node.text === 'arguments') {
    +      count += 1
    +    }
    +    ts.forEachChild(node, visit)
    +  }
    +
    +  visit(sourceFile)
    +  return count
    +}
    +
    +const countAsExpressions = (sourceFile: ts.SourceFile): number => {
    +  let count = 0
    +
    +  const visit = (node: ts.Node): void => {
    +    if (ts.isAsExpression(node)) {
    +      count += 1
    +    }
    +    ts.forEachChild(node, visit)
    +  }
    +
    +  visit(sourceFile)
    +  return count
    +}
    +
    +const hasExportModifier = (node: ts.Node): boolean =>
    +  !!node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
    +
    +const isUnknownTopLevelReturnType = (typeNode: ts.TypeNode): boolean => {
    +  if (typeNode.kind === ts.SyntaxKind.UnknownKeyword) {
    +    return true
    +  }
    +
    +  if (ts.isUnionTypeNode(typeNode)) {
    +    return typeNode.types.some((member) => member.kind === ts.SyntaxKind.UnknownKeyword)
    +  }
    +
    +  return false
    +}
    +
    +const countExportedUnknownReturnTypes = (sourceFile: ts.SourceFile): number => {
    +  let count = 0
    +
    +  const visit = (node: ts.Node): void => {
    +    if (
    +      ts.isFunctionDeclaration(node) &&
    +      hasExportModifier(node) &&
    +      node.type &&
    +      isUnknownTopLevelReturnType(node.type)
    +    ) {
    +      count += 1
    +    }
    +    ts.forEachChild(node, visit)
    +  }
    +
    +  visit(sourceFile)
    +  return count
    +}
    +
    +const countObjectAndEmptyObjectTypeNodes = (
    +  typeNode: ts.TypeNode,
    +): { objectKeywordCount: number; emptyObjectTypeCount: number } => {
    +  let objectKeywordCount = 0
    +  let emptyObjectTypeCount = 0
    +
    +  const visit = (node: ts.Node): void => {
    +    if (node.kind === ts.SyntaxKind.ObjectKeyword) {
    +      objectKeywordCount += 1
    +    }
    +    if (ts.isTypeLiteralNode(node) && node.members.length === 0) {
    +      emptyObjectTypeCount += 1
    +    }
    +    ts.forEachChild(node, visit)
    +  }
    +
    +  visit(typeNode)
    +  return { objectKeywordCount, emptyObjectTypeCount }
    +}
    +
    +const countTypeIdentifierNodes = (typeNode: ts.TypeNode, identifierName: string): number => {
    +  let count = 0
    +
    +  const visit = (node: ts.Node): void => {
    +    if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && node.typeName.text === identifierName) {
    +      count += 1
    +    }
    +    ts.forEachChild(node, visit)
    +  }
    +
    +  visit(typeNode)
    +  return count
    +}
    +
    +const countExportedTypeBroadMarkers = (
    +  sourceFile: ts.SourceFile,
    +): {
    +  objectKeywordCount: number
    +  emptyObjectTypeCount: number
    +  functionWithoutReturnTypeCount: number
    +  phpValueIdentifierCount: number
    +  phpInputIdentifierCount: number
    +} => {
    +  let objectKeywordCount = 0
    +  let emptyObjectTypeCount = 0
    +  let functionWithoutReturnTypeCount = 0
    +  let phpValueIdentifierCount = 0
    +  let phpInputIdentifierCount = 0
    +
    +  const addTypeNodeCounts = (typeNode: ts.TypeNode): void => {
    +    const counts = countObjectAndEmptyObjectTypeNodes(typeNode)
    +    objectKeywordCount += counts.objectKeywordCount
    +    emptyObjectTypeCount += counts.emptyObjectTypeCount
    +    phpValueIdentifierCount += countTypeIdentifierNodes(typeNode, 'PhpValue')
    +    phpInputIdentifierCount += countTypeIdentifierNodes(typeNode, 'PhpInput')
    +  }
    +
    +  sourceFile.forEachChild((node) => {
    +    if (ts.isFunctionDeclaration(node) && hasExportModifier(node)) {
    +      if (node.type) {
    +        addTypeNodeCounts(node.type)
    +      } else if (node.body) {
    +        functionWithoutReturnTypeCount += 1
    +      }
    +
    +      for (const parameter of node.parameters) {
    +        if (parameter.type) {
    +          addTypeNodeCounts(parameter.type)
    +        }
    +      }
    +
    +      for (const typeParameter of node.typeParameters ?? []) {
    +        if (typeParameter.constraint) {
    +          addTypeNodeCounts(typeParameter.constraint)
    +        }
    +        if (typeParameter.default) {
    +          addTypeNodeCounts(typeParameter.default)
    +        }
    +      }
    +      return
    +    }
    +
    +    if (ts.isVariableStatement(node) && hasExportModifier(node)) {
    +      for (const declaration of node.declarationList.declarations) {
    +        if (declaration.type) {
    +          addTypeNodeCounts(declaration.type)
    +        }
    +      }
    +      return
    +    }
    +
    +    // Intentionally scoped to exported runtime APIs (functions/vars).
    +    // Type aliases/interfaces are tracked separately and can evolve in staged passes.
    +  })
    +
    +  return {
    +    objectKeywordCount,
    +    emptyObjectTypeCount,
    +    functionWithoutReturnTypeCount,
    +    phpValueIdentifierCount,
    +    phpInputIdentifierCount,
    +  }
    +}
    +
    +const countExportedStringSortFlagParams = (sourceFile: ts.SourceFile): number => {
    +  let count = 0
    +  const sortFlagParamNames = new Set(['sortFlags', 'sortFlag'])
    +
    +  sourceFile.forEachChild((node) => {
    +    if (!ts.isFunctionDeclaration(node) || !hasExportModifier(node)) {
    +      return
    +    }
    +
    +    for (const parameter of node.parameters) {
    +      if (!ts.isIdentifier(parameter.name) || !sortFlagParamNames.has(parameter.name.text) || !parameter.type) {
    +        continue
    +      }
    +      if (parameter.type.kind === ts.SyntaxKind.StringKeyword) {
    +        count += 1
    +      }
    +    }
    +  })
    +
    +  return count
    +}
    +
    +const countExportedStringModeCaseParams = (sourceFile: ts.SourceFile): number => {
    +  let count = 0
    +  const modeCaseParamNames = new Set(['mode', 'cs'])
    +
    +  sourceFile.forEachChild((node) => {
    +    if (!ts.isFunctionDeclaration(node) || !hasExportModifier(node)) {
    +      return
    +    }
    +
    +    for (const parameter of node.parameters) {
    +      if (!ts.isIdentifier(parameter.name) || !modeCaseParamNames.has(parameter.name.text) || !parameter.type) {
    +        continue
    +      }
    +      if (parameter.type.kind === ts.SyntaxKind.StringKeyword) {
    +        count += 1
    +      }
    +    }
    +  })
    +
    +  return count
    +}
    +
    +for (const filePath of sourceFiles) {
    +  const sourceText = fs.readFileSync(filePath, 'utf8')
    +
    +  if (/@ts-nocheck\b/.test(sourceText)) {
    +    tsNoCheckFiles.push(path.relative(cwd, filePath))
    +  }
    +
    +  const tsIgnoreCount = (sourceText.match(/@ts-ignore\b/g) || []).length
    +  if (tsIgnoreCount > 0) {
    +    tsIgnoreFindings.push({
    +      file: path.relative(cwd, filePath),
    +      count: tsIgnoreCount,
    +    })
    +  }
    +
    +  const tsExpectErrorCount = (sourceText.match(/@ts-expect-error\b/g) || []).length
    +  if (tsExpectErrorCount > 0) {
    +    tsExpectErrorFindings.push({
    +      file: path.relative(cwd, filePath),
    +      count: tsExpectErrorCount,
    +    })
    +  }
    +
    +  const recordStringUnknownCount = (sourceText.match(/\bRecord\s*<\s*string\s*,\s*unknown\s*>/g) || []).length
    +  if (recordStringUnknownCount > 0) {
    +    recordStringUnknownFindings.push({
    +      file: path.relative(cwd, filePath),
    +      count: recordStringUnknownCount,
    +    })
    +  }
    +
    +  const asUnknownAsCount = (sourceText.match(/\bas\s+unknown\s+as\b/g) || []).length
    +  if (asUnknownAsCount > 0) {
    +    asUnknownAsFindings.push({
    +      file: path.relative(cwd, filePath),
    +      count: asUnknownAsCount,
    +    })
    +  }
    +
    +  const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
    +  const functionTypeCount = countFunctionTypeReferences(sourceFile)
    +  if (functionTypeCount > 0) {
    +    functionTypeFindings.push({
    +      file: path.relative(cwd, filePath),
    +      count: functionTypeCount,
    +    })
    +  }
    +
    +  const isPhpFile = filePath.includes(`${path.sep}src${path.sep}php${path.sep}`)
    +  if (!isPhpFile) {
    +    const nonPhpExportedTypeBroadCounts = countExportedTypeBroadMarkers(sourceFile)
    +    srcNonPhpExportedFunctionWithoutReturnTypeCount += nonPhpExportedTypeBroadCounts.functionWithoutReturnTypeCount
    +    if (nonPhpExportedTypeBroadCounts.functionWithoutReturnTypeCount > 0) {
    +      nonPhpExportedFunctionWithoutReturnTypeFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: nonPhpExportedTypeBroadCounts.functionWithoutReturnTypeCount,
    +      })
    +    }
    +
    +    const nonPhpLocalNullishAliasCount = (
    +      sourceText.match(/\btype\s+[A-Za-z0-9_]+\s*=\s*\{\}\s*\|\s*null\s*\|\s*undefined\b/g) || []
    +    ).length
    +    srcNonPhpLocalNullishAliasCount += nonPhpLocalNullishAliasCount
    +    if (nonPhpLocalNullishAliasCount > 0) {
    +      nonPhpLocalNullishAliasFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: nonPhpLocalNullishAliasCount,
    +      })
    +    }
    +
    +    const nonPhpLocalObjectAliasCount = (sourceText.match(/^type\s+[A-Za-z0-9_]+\s*=\s*object\s*$/gm) || []).length
    +    srcNonPhpLocalObjectAliasCount += nonPhpLocalObjectAliasCount
    +    if (nonPhpLocalObjectAliasCount > 0) {
    +      nonPhpLocalObjectAliasFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: nonPhpLocalObjectAliasCount,
    +      })
    +    }
    +
    +    const nonPhpLocalUnknownAliasCount = (sourceText.match(/^type\s+[A-Za-z0-9_]+\s*=\s*unknown\s*$/gm) || []).length
    +    srcNonPhpLocalUnknownAliasCount += nonPhpLocalUnknownAliasCount
    +    if (nonPhpLocalUnknownAliasCount > 0) {
    +      nonPhpLocalUnknownAliasFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: nonPhpLocalUnknownAliasCount,
    +      })
    +    }
    +  }
    +
    +  if (isPhpFile) {
    +    const reflectGetSetCount = (sourceText.match(/\bReflect\.(?:get|set)\s*\(/g) || []).length
    +    srcPhpReflectGetSetCount += reflectGetSetCount
    +    if (reflectGetSetCount > 0) {
    +      reflectGetSetFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: reflectGetSetCount,
    +      })
    +    }
    +
    +    const globalThisIdentifierCount = (sourceText.match(/\bglobalThis\b/g) || []).length
    +    srcPhpGlobalThisIdentifierCount += globalThisIdentifierCount
    +    if (globalThisIdentifierCount > 0) {
    +      globalThisIdentifierFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: globalThisIdentifierCount,
    +      })
    +    }
    +
    +    const srcPhpValueCount = (sourceText.match(/\bPhpValue\b/g) || []).length
    +    srcPhpPhpValueIdentifierCount += srcPhpValueCount
    +    if (srcPhpValueCount > 0) {
    +      phpValueIdentifierFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: srcPhpValueCount,
    +      })
    +    }
    +
    +    const inlineNullishAliasCount = (
    +      sourceText.match(/\btype\s+[A-Za-z0-9_]+\s*=\s*\{\}\s*\|\s*null\s*\|\s*undefined\b/g) || []
    +    ).length
    +    srcPhpInlineNullishAliasCount += inlineNullishAliasCount
    +    if (inlineNullishAliasCount > 0) {
    +      inlineNullishAliasFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: inlineNullishAliasCount,
    +      })
    +    }
    +
    +    const srcPhpUnknownCount = (sourceText.match(/\bunknown\b/g) || []).length
    +    srcPhpUnknownKeywordCount += srcPhpUnknownCount
    +    const srcPhpMixedCount = (sourceText.match(/\bPhpMixed\b/g) || []).length
    +    srcPhpPhpMixedKeywordCount += srcPhpMixedCount
    +    if (srcPhpMixedCount > 0) {
    +      phpMixedFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: srcPhpMixedCount,
    +      })
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}array${path.sep}`)) {
    +      srcPhpArrayUnknownKeywordCount += srcPhpUnknownCount
    +      srcPhpArrayPhpMixedKeywordCount += srcPhpMixedCount
    +      if (srcPhpMixedCount > 0) {
    +        arrayPhpMixedFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: srcPhpMixedCount,
    +        })
    +      }
    +      const arrayAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpArrayAsExpressionCount += arrayAsExpressionCount
    +      if (arrayAsExpressionCount > 0) {
    +        arrayAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: arrayAsExpressionCount,
    +        })
    +      }
    +
    +      const arrayExportedSortFlagStringParamCount = countExportedStringSortFlagParams(sourceFile)
    +      srcPhpArrayExportedSortFlagStringParamCount += arrayExportedSortFlagStringParamCount
    +      if (arrayExportedSortFlagStringParamCount > 0) {
    +        arrayExportedSortFlagStringParamFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: arrayExportedSortFlagStringParamCount,
    +        })
    +      }
    +
    +      const arrayExportedModeCaseStringParamCount = countExportedStringModeCaseParams(sourceFile)
    +      srcPhpArrayExportedModeCaseStringParamCount += arrayExportedModeCaseStringParamCount
    +      if (arrayExportedModeCaseStringParamCount > 0) {
    +        arrayExportedModeCaseStringParamFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: arrayExportedModeCaseStringParamCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}var${path.sep}`)) {
    +      srcPhpVarUnknownKeywordCount += srcPhpUnknownCount
    +      srcPhpVarPhpMixedKeywordCount += srcPhpMixedCount
    +      if (srcPhpMixedCount > 0) {
    +        varPhpMixedFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: srcPhpMixedCount,
    +        })
    +      }
    +      const varAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpVarAsExpressionCount += varAsExpressionCount
    +      if (varAsExpressionCount > 0) {
    +        varAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: varAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}strings${path.sep}`)) {
    +      srcPhpStringsPhpMixedKeywordCount += srcPhpMixedCount
    +      if (srcPhpMixedCount > 0) {
    +        stringsPhpMixedFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: srcPhpMixedCount,
    +        })
    +      }
    +      const stringsAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpStringsAsExpressionCount += stringsAsExpressionCount
    +      if (stringsAsExpressionCount > 0) {
    +        stringsAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: stringsAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}ctype${path.sep}`)) {
    +      const ctypeAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpCtypeAsExpressionCount += ctypeAsExpressionCount
    +      if (ctypeAsExpressionCount > 0) {
    +        ctypeAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: ctypeAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}info${path.sep}`)) {
    +      const infoAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpInfoAsExpressionCount += infoAsExpressionCount
    +      if (infoAsExpressionCount > 0) {
    +        infoAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: infoAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}_helpers${path.sep}`)) {
    +      const helpersAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpHelpersAsExpressionCount += helpersAsExpressionCount
    +      if (helpersAsExpressionCount > 0) {
    +        helpersAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: helpersAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}url${path.sep}`)) {
    +      const urlAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpUrlAsExpressionCount += urlAsExpressionCount
    +      if (urlAsExpressionCount > 0) {
    +        urlAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: urlAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}funchand${path.sep}`)) {
    +      const funchandAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpFunchandAsExpressionCount += funchandAsExpressionCount
    +      if (funchandAsExpressionCount > 0) {
    +        funchandAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: funchandAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}json${path.sep}`)) {
    +      const jsonAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpJsonAsExpressionCount += jsonAsExpressionCount
    +      if (jsonAsExpressionCount > 0) {
    +        jsonAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: jsonAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}datetime${path.sep}`)) {
    +      const datetimeAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpDatetimeAsExpressionCount += datetimeAsExpressionCount
    +      if (datetimeAsExpressionCount > 0) {
    +        datetimeAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: datetimeAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}bc${path.sep}`)) {
    +      const bcAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpBcAsExpressionCount += bcAsExpressionCount
    +      if (bcAsExpressionCount > 0) {
    +        bcAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: bcAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}filesystem${path.sep}`)) {
    +      const filesystemAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpFilesystemAsExpressionCount += filesystemAsExpressionCount
    +      if (filesystemAsExpressionCount > 0) {
    +        filesystemAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: filesystemAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}misc${path.sep}`)) {
    +      const miscAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpMiscAsExpressionCount += miscAsExpressionCount
    +      if (miscAsExpressionCount > 0) {
    +        miscAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: miscAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}pcre${path.sep}`)) {
    +      const pcreAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpPcreAsExpressionCount += pcreAsExpressionCount
    +      if (pcreAsExpressionCount > 0) {
    +        pcreAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: pcreAsExpressionCount,
    +        })
    +      }
    +    }
    +    if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}xdiff${path.sep}`)) {
    +      const xdiffAsExpressionCount = countAsExpressions(sourceFile)
    +      srcPhpXdiffAsExpressionCount += xdiffAsExpressionCount
    +      if (xdiffAsExpressionCount > 0) {
    +        xdiffAsExpressionFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: xdiffAsExpressionCount,
    +        })
    +      }
    +    }
    +
    +    const rawIndexSignatureUnknownCount = (sourceText.match(/\{\s*\[\s*key\s*:\s*string\s*]\s*:\s*unknown\s*}/g) || [])
    +      .length
    +    srcPhpRawIndexSignatureUnknownCount += rawIndexSignatureUnknownCount
    +
    +    const localPhpValueAliasCount = filePath.endsWith(
    +      `${path.sep}src${path.sep}php${path.sep}_helpers${path.sep}_phpTypes.ts`,
    +    )
    +      ? 0
    +      : (sourceText.match(/\btype\s+PhpValue\s*=\s*\{\}\s*\|\s*null\s*\|\s*undefined\b/g) || []).length
    +    srcPhpLocalPhpValueAliasCount += localPhpValueAliasCount
    +    if (localPhpValueAliasCount > 0) {
    +      localPhpValueAliasFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: localPhpValueAliasCount,
    +      })
    +    }
    +
    +    const localPhpInputAliasOutsideHelpersCount = filePath.includes(
    +      `${path.sep}src${path.sep}php${path.sep}_helpers${path.sep}`,
    +    )
    +      ? 0
    +      : (sourceText.match(/^type\s+[A-Za-z0-9_]+\s*=\s*PhpInput\s*$/gm) || []).length
    +    srcPhpLocalPhpInputAliasOutsideHelpersCount += localPhpInputAliasOutsideHelpersCount
    +    if (localPhpInputAliasOutsideHelpersCount > 0) {
    +      localPhpInputAliasOutsideHelpersFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: localPhpInputAliasOutsideHelpersCount,
    +      })
    +    }
    +
    +    const directIniGlobalReadCount = (sourceText.match(/\$locutus\?\.\s*php\?\.\s*ini\b/g) || []).length
    +    srcPhpDirectIniGlobalReadCount += directIniGlobalReadCount
    +    if (directIniGlobalReadCount > 0) {
    +      directIniGlobalReadFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: directIniGlobalReadCount,
    +      })
    +    }
    +
    +    const argumentsIdentifierCount = countArgumentsIdentifiers(sourceFile)
    +    if (argumentsIdentifierCount > 0) {
    +      argumentsIdentifierFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: argumentsIdentifierCount,
    +      })
    +    }
    +
    +    const exportedUnknownReturnTypeCount = countExportedUnknownReturnTypes(sourceFile)
    +    srcPhpExportedUnknownReturnTypeCount += exportedUnknownReturnTypeCount
    +    if (exportedUnknownReturnTypeCount > 0) {
    +      exportedUnknownReturnTypeFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: exportedUnknownReturnTypeCount,
    +      })
    +    }
    +
    +    const exportedTypeBroadCounts = countExportedTypeBroadMarkers(sourceFile)
    +
    +    srcPhpExportedObjectKeywordCount += exportedTypeBroadCounts.objectKeywordCount
    +    if (exportedTypeBroadCounts.objectKeywordCount > 0) {
    +      exportedObjectKeywordFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: exportedTypeBroadCounts.objectKeywordCount,
    +      })
    +    }
    +
    +    srcPhpExportedEmptyObjectTypeCount += exportedTypeBroadCounts.emptyObjectTypeCount
    +    if (exportedTypeBroadCounts.emptyObjectTypeCount > 0) {
    +      exportedEmptyObjectTypeFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: exportedTypeBroadCounts.emptyObjectTypeCount,
    +      })
    +    }
    +
    +    srcPhpExportedFunctionWithoutReturnTypeCount += exportedTypeBroadCounts.functionWithoutReturnTypeCount
    +    if (exportedTypeBroadCounts.functionWithoutReturnTypeCount > 0) {
    +      exportedFunctionWithoutReturnTypeFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: exportedTypeBroadCounts.functionWithoutReturnTypeCount,
    +      })
    +    }
    +
    +    srcPhpExportedPhpValueIdentifierCount += exportedTypeBroadCounts.phpValueIdentifierCount
    +    if (exportedTypeBroadCounts.phpValueIdentifierCount > 0) {
    +      exportedPhpValueIdentifierFindings.push({
    +        file: path.relative(cwd, filePath),
    +        count: exportedTypeBroadCounts.phpValueIdentifierCount,
    +      })
    +      if (filePath.includes(`${path.sep}src${path.sep}php${path.sep}array${path.sep}`)) {
    +        srcPhpArrayExportedPhpValueIdentifierCount += exportedTypeBroadCounts.phpValueIdentifierCount
    +        arrayExportedPhpValueIdentifierFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: exportedTypeBroadCounts.phpValueIdentifierCount,
    +        })
    +      }
    +    }
    +
    +    const isHelpersFile = filePath.includes(`${path.sep}src${path.sep}php${path.sep}_helpers${path.sep}`)
    +    if (!isHelpersFile) {
    +      srcPhpExportedPhpInputOutsideHelpersCount += exportedTypeBroadCounts.phpInputIdentifierCount
    +      if (exportedTypeBroadCounts.phpInputIdentifierCount > 0) {
    +        exportedPhpInputOutsideHelpersFindings.push({
    +          file: path.relative(cwd, filePath),
    +          count: exportedTypeBroadCounts.phpInputIdentifierCount,
    +        })
    +      }
    +    }
    +  }
    +}
    +
    +let hasFailure = false
    +
    +if (tsNoCheckFiles.length > 0) {
    +  hasFailure = true
    +  console.error(`Forbidden @ts-nocheck directives found in ${tsNoCheckFiles.length} file(s):`)
    +  for (const file of tsNoCheckFiles) {
    +    console.error(`  - ${file}`)
    +  }
    +}
    +
    +if (tsExpectErrorFindings.length > 0) {
    +  hasFailure = true
    +  const total = tsExpectErrorFindings.reduce((sum, finding) => sum + finding.count, 0)
    +  console.error(`Forbidden @ts-expect-error directives found: ${total}`)
    +  for (const finding of tsExpectErrorFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (tsIgnoreFindings.length > 0) {
    +  hasFailure = true
    +  const total = tsIgnoreFindings.reduce((sum, finding) => sum + finding.count, 0)
    +  console.error(`Forbidden @ts-ignore directives found: ${total}`)
    +  for (const finding of tsIgnoreFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (recordStringUnknownFindings.length > 0) {
    +  hasFailure = true
    +  const total = recordStringUnknownFindings.reduce((sum, finding) => sum + finding.count, 0)
    +  console.error(`Forbidden Record<string, unknown> usages found: ${total}`)
    +  for (const finding of recordStringUnknownFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (functionTypeFindings.length > 0) {
    +  hasFailure = true
    +  const total = functionTypeFindings.reduce((sum, finding) => sum + finding.count, 0)
    +  console.error(`Forbidden Function type usages found: ${total}`)
    +  for (const finding of functionTypeFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (asUnknownAsFindings.length > 0) {
    +  hasFailure = true
    +  const total = asUnknownAsFindings.reduce((sum, finding) => sum + finding.count, 0)
    +  console.error(`Forbidden 'as unknown as' casts found: ${total}`)
    +  for (const finding of asUnknownAsFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (argumentsIdentifierFindings.length > 0) {
    +  hasFailure = true
    +  const total = argumentsIdentifierFindings.reduce((sum, finding) => sum + finding.count, 0)
    +  console.error(`Forbidden 'arguments' identifier usages in src/php found: ${total}`)
    +  for (const finding of argumentsIdentifierFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpRawIndexSignatureUnknownCount > MAX_SRC_PHP_RAW_INDEX_SIGNATURE_UNKNOWN) {
    +  hasFailure = true
    +  console.error(
    +    `src/php raw '{ [key: string]: unknown }' count increased: ${srcPhpRawIndexSignatureUnknownCount} > ${MAX_SRC_PHP_RAW_INDEX_SIGNATURE_UNKNOWN}`,
    +  )
    +}
    +
    +if (srcPhpExportedUnknownReturnTypeCount > MAX_SRC_PHP_EXPORTED_UNKNOWN_RETURN_TYPES) {
    +  hasFailure = true
    +  console.error(
    +    `src/php exported unknown return-type count increased: ${srcPhpExportedUnknownReturnTypeCount} > ${MAX_SRC_PHP_EXPORTED_UNKNOWN_RETURN_TYPES}`,
    +  )
    +  for (const finding of exportedUnknownReturnTypeFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpExportedObjectKeywordCount > MAX_SRC_PHP_EXPORTED_OBJECT_KEYWORD) {
    +  hasFailure = true
    +  console.error(
    +    `src/php exported signature 'object' keyword count increased: ${srcPhpExportedObjectKeywordCount} > ${MAX_SRC_PHP_EXPORTED_OBJECT_KEYWORD}`,
    +  )
    +  for (const finding of exportedObjectKeywordFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpExportedEmptyObjectTypeCount > MAX_SRC_PHP_EXPORTED_EMPTY_OBJECT_TYPE) {
    +  hasFailure = true
    +  console.error(
    +    `src/php exported signature empty-object '{}' count increased: ${srcPhpExportedEmptyObjectTypeCount} > ${MAX_SRC_PHP_EXPORTED_EMPTY_OBJECT_TYPE}`,
    +  )
    +  for (const finding of exportedEmptyObjectTypeFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpExportedFunctionWithoutReturnTypeCount > MAX_SRC_PHP_EXPORTED_FUNCTION_WITHOUT_RETURN_TYPE) {
    +  hasFailure = true
    +  console.error(
    +    `src/php exported function-without-return-type count increased: ${srcPhpExportedFunctionWithoutReturnTypeCount} > ${MAX_SRC_PHP_EXPORTED_FUNCTION_WITHOUT_RETURN_TYPE}`,
    +  )
    +  for (const finding of exportedFunctionWithoutReturnTypeFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcNonPhpExportedFunctionWithoutReturnTypeCount > MAX_SRC_NON_PHP_EXPORTED_FUNCTION_WITHOUT_RETURN_TYPE) {
    +  hasFailure = true
    +  console.error(
    +    `src non-php exported function-without-return-type count increased: ${srcNonPhpExportedFunctionWithoutReturnTypeCount} > ${MAX_SRC_NON_PHP_EXPORTED_FUNCTION_WITHOUT_RETURN_TYPE}`,
    +  )
    +  for (const finding of nonPhpExportedFunctionWithoutReturnTypeFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcNonPhpLocalNullishAliasCount > MAX_SRC_NON_PHP_LOCAL_NULLISH_ALIAS) {
    +  hasFailure = true
    +  console.error(
    +    `src non-php local 'type X = {} | null | undefined' alias count increased: ${srcNonPhpLocalNullishAliasCount} > ${MAX_SRC_NON_PHP_LOCAL_NULLISH_ALIAS}`,
    +  )
    +  for (const finding of nonPhpLocalNullishAliasFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcNonPhpLocalObjectAliasCount > MAX_SRC_NON_PHP_LOCAL_OBJECT_ALIAS) {
    +  hasFailure = true
    +  console.error(
    +    `src non-php local 'type X = object' alias count increased: ${srcNonPhpLocalObjectAliasCount} > ${MAX_SRC_NON_PHP_LOCAL_OBJECT_ALIAS}`,
    +  )
    +  for (const finding of nonPhpLocalObjectAliasFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcNonPhpLocalUnknownAliasCount > MAX_SRC_NON_PHP_LOCAL_UNKNOWN_ALIAS) {
    +  hasFailure = true
    +  console.error(
    +    `src non-php local 'type X = unknown' alias count increased: ${srcNonPhpLocalUnknownAliasCount} > ${MAX_SRC_NON_PHP_LOCAL_UNKNOWN_ALIAS}`,
    +  )
    +  for (const finding of nonPhpLocalUnknownAliasFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpExportedPhpValueIdentifierCount > MAX_SRC_PHP_EXPORTED_PHPVALUE_IDENTIFIER) {
    +  hasFailure = true
    +  console.error(
    +    `src/php exported signature 'PhpValue' identifier count increased: ${srcPhpExportedPhpValueIdentifierCount} > ${MAX_SRC_PHP_EXPORTED_PHPVALUE_IDENTIFIER}`,
    +  )
    +  for (const finding of exportedPhpValueIdentifierFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpExportedPhpInputOutsideHelpersCount > MAX_SRC_PHP_EXPORTED_PHPINPUT_OUTSIDE_HELPERS) {
    +  hasFailure = true
    +  console.error(
    +    `src/php exported signature 'PhpInput' identifier count outside _helpers increased: ${srcPhpExportedPhpInputOutsideHelpersCount} > ${MAX_SRC_PHP_EXPORTED_PHPINPUT_OUTSIDE_HELPERS}`,
    +  )
    +  for (const finding of exportedPhpInputOutsideHelpersFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpArrayExportedPhpValueIdentifierCount > MAX_SRC_PHP_ARRAY_EXPORTED_PHPVALUE_IDENTIFIER) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/array exported signature 'PhpValue' identifier count increased: ${srcPhpArrayExportedPhpValueIdentifierCount} > ${MAX_SRC_PHP_ARRAY_EXPORTED_PHPVALUE_IDENTIFIER}`,
    +  )
    +  for (const finding of arrayExportedPhpValueIdentifierFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpArrayExportedSortFlagStringParamCount > MAX_SRC_PHP_ARRAY_EXPORTED_SORTFLAG_STRING_PARAM) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/array exported sort-flag string parameter count increased: ${srcPhpArrayExportedSortFlagStringParamCount} > ${MAX_SRC_PHP_ARRAY_EXPORTED_SORTFLAG_STRING_PARAM}`,
    +  )
    +  for (const finding of arrayExportedSortFlagStringParamFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpArrayExportedModeCaseStringParamCount > MAX_SRC_PHP_ARRAY_EXPORTED_MODE_CASE_STRING_PARAM) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/array exported mode/case string parameter count increased: ${srcPhpArrayExportedModeCaseStringParamCount} > ${MAX_SRC_PHP_ARRAY_EXPORTED_MODE_CASE_STRING_PARAM}`,
    +  )
    +  for (const finding of arrayExportedModeCaseStringParamFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpUnknownKeywordCount > MAX_SRC_PHP_UNKNOWN_KEYWORD) {
    +  hasFailure = true
    +  console.error(
    +    `src/php 'unknown' keyword count increased: ${srcPhpUnknownKeywordCount} > ${MAX_SRC_PHP_UNKNOWN_KEYWORD}`,
    +  )
    +}
    +
    +if (srcPhpPhpMixedKeywordCount > MAX_SRC_PHP_PHPMIXED_KEYWORD) {
    +  hasFailure = true
    +  console.error(
    +    `src/php 'PhpMixed' keyword count increased: ${srcPhpPhpMixedKeywordCount} > ${MAX_SRC_PHP_PHPMIXED_KEYWORD}`,
    +  )
    +  for (const finding of phpMixedFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpArrayUnknownKeywordCount > MAX_SRC_PHP_ARRAY_UNKNOWN_KEYWORD) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/array 'unknown' keyword count increased: ${srcPhpArrayUnknownKeywordCount} > ${MAX_SRC_PHP_ARRAY_UNKNOWN_KEYWORD}`,
    +  )
    +}
    +
    +if (srcPhpArrayPhpMixedKeywordCount > MAX_SRC_PHP_ARRAY_PHPMIXED_KEYWORD) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/array 'PhpMixed' keyword count increased: ${srcPhpArrayPhpMixedKeywordCount} > ${MAX_SRC_PHP_ARRAY_PHPMIXED_KEYWORD}`,
    +  )
    +  for (const finding of arrayPhpMixedFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpArrayAsExpressionCount > MAX_SRC_PHP_ARRAY_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/array 'as' expression count increased: ${srcPhpArrayAsExpressionCount} > ${MAX_SRC_PHP_ARRAY_AS_EXPRESSION}`,
    +  )
    +  for (const finding of arrayAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpVarUnknownKeywordCount > MAX_SRC_PHP_VAR_UNKNOWN_KEYWORD) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/var 'unknown' keyword count increased: ${srcPhpVarUnknownKeywordCount} > ${MAX_SRC_PHP_VAR_UNKNOWN_KEYWORD}`,
    +  )
    +}
    +
    +if (srcPhpVarPhpMixedKeywordCount > MAX_SRC_PHP_VAR_PHPMIXED_KEYWORD) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/var 'PhpMixed' keyword count increased: ${srcPhpVarPhpMixedKeywordCount} > ${MAX_SRC_PHP_VAR_PHPMIXED_KEYWORD}`,
    +  )
    +  for (const finding of varPhpMixedFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpVarAsExpressionCount > MAX_SRC_PHP_VAR_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/var 'as' expression count increased: ${srcPhpVarAsExpressionCount} > ${MAX_SRC_PHP_VAR_AS_EXPRESSION}`,
    +  )
    +  for (const finding of varAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpStringsAsExpressionCount > MAX_SRC_PHP_STRINGS_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/strings 'as' expression count increased: ${srcPhpStringsAsExpressionCount} > ${MAX_SRC_PHP_STRINGS_AS_EXPRESSION}`,
    +  )
    +  for (const finding of stringsAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpStringsPhpMixedKeywordCount > MAX_SRC_PHP_STRINGS_PHPMIXED_KEYWORD) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/strings 'PhpMixed' keyword count increased: ${srcPhpStringsPhpMixedKeywordCount} > ${MAX_SRC_PHP_STRINGS_PHPMIXED_KEYWORD}`,
    +  )
    +  for (const finding of stringsPhpMixedFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpCtypeAsExpressionCount > MAX_SRC_PHP_CTYPE_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/ctype 'as' expression count increased: ${srcPhpCtypeAsExpressionCount} > ${MAX_SRC_PHP_CTYPE_AS_EXPRESSION}`,
    +  )
    +  for (const finding of ctypeAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpInfoAsExpressionCount > MAX_SRC_PHP_INFO_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/info 'as' expression count increased: ${srcPhpInfoAsExpressionCount} > ${MAX_SRC_PHP_INFO_AS_EXPRESSION}`,
    +  )
    +  for (const finding of infoAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpHelpersAsExpressionCount > MAX_SRC_PHP_HELPERS_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/_helpers 'as' expression count increased: ${srcPhpHelpersAsExpressionCount} > ${MAX_SRC_PHP_HELPERS_AS_EXPRESSION}`,
    +  )
    +  for (const finding of helpersAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpUrlAsExpressionCount > MAX_SRC_PHP_URL_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/url 'as' expression count increased: ${srcPhpUrlAsExpressionCount} > ${MAX_SRC_PHP_URL_AS_EXPRESSION}`,
    +  )
    +  for (const finding of urlAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpFunchandAsExpressionCount > MAX_SRC_PHP_FUNCHAND_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/funchand 'as' expression count increased: ${srcPhpFunchandAsExpressionCount} > ${MAX_SRC_PHP_FUNCHAND_AS_EXPRESSION}`,
    +  )
    +  for (const finding of funchandAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpJsonAsExpressionCount > MAX_SRC_PHP_JSON_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/json 'as' expression count increased: ${srcPhpJsonAsExpressionCount} > ${MAX_SRC_PHP_JSON_AS_EXPRESSION}`,
    +  )
    +  for (const finding of jsonAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpDatetimeAsExpressionCount > MAX_SRC_PHP_DATETIME_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/datetime 'as' expression count increased: ${srcPhpDatetimeAsExpressionCount} > ${MAX_SRC_PHP_DATETIME_AS_EXPRESSION}`,
    +  )
    +  for (const finding of datetimeAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpBcAsExpressionCount > MAX_SRC_PHP_BC_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/bc 'as' expression count increased: ${srcPhpBcAsExpressionCount} > ${MAX_SRC_PHP_BC_AS_EXPRESSION}`,
    +  )
    +  for (const finding of bcAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpFilesystemAsExpressionCount > MAX_SRC_PHP_FILESYSTEM_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/filesystem 'as' expression count increased: ${srcPhpFilesystemAsExpressionCount} > ${MAX_SRC_PHP_FILESYSTEM_AS_EXPRESSION}`,
    +  )
    +  for (const finding of filesystemAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpMiscAsExpressionCount > MAX_SRC_PHP_MISC_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/misc 'as' expression count increased: ${srcPhpMiscAsExpressionCount} > ${MAX_SRC_PHP_MISC_AS_EXPRESSION}`,
    +  )
    +  for (const finding of miscAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpPcreAsExpressionCount > MAX_SRC_PHP_PCRE_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/pcre 'as' expression count increased: ${srcPhpPcreAsExpressionCount} > ${MAX_SRC_PHP_PCRE_AS_EXPRESSION}`,
    +  )
    +  for (const finding of pcreAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpXdiffAsExpressionCount > MAX_SRC_PHP_XDIFF_AS_EXPRESSION) {
    +  hasFailure = true
    +  console.error(
    +    `src/php/xdiff 'as' expression count increased: ${srcPhpXdiffAsExpressionCount} > ${MAX_SRC_PHP_XDIFF_AS_EXPRESSION}`,
    +  )
    +  for (const finding of xdiffAsExpressionFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpLocalPhpValueAliasCount > MAX_SRC_PHP_LOCAL_PHPVALUE_ALIAS) {
    +  hasFailure = true
    +  console.error(
    +    `src/php local 'type PhpValue = {} | null | undefined' alias count increased: ${srcPhpLocalPhpValueAliasCount} > ${MAX_SRC_PHP_LOCAL_PHPVALUE_ALIAS}`,
    +  )
    +  for (const finding of localPhpValueAliasFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpLocalPhpInputAliasOutsideHelpersCount > MAX_SRC_PHP_LOCAL_PHPINPUT_ALIAS_OUTSIDE_HELPERS) {
    +  hasFailure = true
    +  console.error(
    +    `src/php local 'type X = PhpInput' alias count outside _helpers increased: ${srcPhpLocalPhpInputAliasOutsideHelpersCount} > ${MAX_SRC_PHP_LOCAL_PHPINPUT_ALIAS_OUTSIDE_HELPERS}`,
    +  )
    +  for (const finding of localPhpInputAliasOutsideHelpersFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpDirectIniGlobalReadCount > MAX_SRC_PHP_DIRECT_INI_GLOBAL_READS) {
    +  hasFailure = true
    +  console.error(
    +    `src/php direct '$locutus?.php?.ini' read count increased: ${srcPhpDirectIniGlobalReadCount} > ${MAX_SRC_PHP_DIRECT_INI_GLOBAL_READS}`,
    +  )
    +  for (const finding of directIniGlobalReadFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpPhpValueIdentifierCount > MAX_SRC_PHP_PHPVALUE_IDENTIFIER) {
    +  hasFailure = true
    +  console.error(
    +    `src/php 'PhpValue' identifier count increased: ${srcPhpPhpValueIdentifierCount} > ${MAX_SRC_PHP_PHPVALUE_IDENTIFIER}`,
    +  )
    +  for (const finding of phpValueIdentifierFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpInlineNullishAliasCount > MAX_SRC_PHP_INLINE_NULLISH_ALIAS) {
    +  hasFailure = true
    +  console.error(
    +    `src/php inline '{}' | null | undefined alias count increased: ${srcPhpInlineNullishAliasCount} > ${MAX_SRC_PHP_INLINE_NULLISH_ALIAS}`,
    +  )
    +  for (const finding of inlineNullishAliasFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpReflectGetSetCount > MAX_SRC_PHP_REFLECT_GET_SET) {
    +  hasFailure = true
    +  console.error(`src/php Reflect.get/set count increased: ${srcPhpReflectGetSetCount} > ${MAX_SRC_PHP_REFLECT_GET_SET}`)
    +  for (const finding of reflectGetSetFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (srcPhpGlobalThisIdentifierCount > MAX_SRC_PHP_GLOBALTHIS_IDENTIFIER) {
    +  hasFailure = true
    +  console.error(
    +    `src/php globalThis identifier count increased: ${srcPhpGlobalThisIdentifierCount} > ${MAX_SRC_PHP_GLOBALTHIS_IDENTIFIER}`,
    +  )
    +  for (const finding of globalThisIdentifierFindings) {
    +    console.error(`  - ${finding.file}: ${finding.count}`)
    +  }
    +}
    +
    +if (hasFailure) {
    +  process.exit(1)
    +}
    +
    +console.log(
    +  'ts debt policy ok: @ts-nocheck 0, @ts-ignore 0, @ts-expect-error 0, Function type 0, Record<string, unknown> 0, as unknown as 0, src/php arguments 0, src/php raw index-signature unknown not increased, src/php exported unknown return-types not increased, src/php exported object keyword count not increased, src/php exported empty-object count not increased, src/php exported missing return-type count not increased, src non-php exported missing return-type count not increased, src non-php local nullish/object alias count not increased, src non-php local object alias count not increased, src non-php local unknown alias count not increased, src/php exported PhpValue identifier count not increased, src/php exported PhpInput identifier count outside _helpers not increased, src/php/array exported PhpValue identifier count not increased, src/php/array exported sort-flag string parameter count not increased, src/php/array exported mode/case string parameter count not increased, src/php unknown keyword count not increased, src/php PhpMixed keyword count not increased, src/php/array unknown keyword count not increased, src/php/array PhpMixed keyword count not increased, src/php/array as-expression count not increased, src/php/var unknown keyword count not increased, src/php/var PhpMixed keyword count not increased, src/php/var as-expression count not increased, src/php/strings as-expression count not increased, src/php/strings PhpMixed keyword count not increased, src/php/ctype as-expression count not increased, src/php/info as-expression count not increased, src/php/_helpers as-expression count not increased, src/php/url as-expression count not increased, src/php/funchand as-expression count not increased, src/php/json as-expression count not increased, src/php/datetime as-expression count not increased, src/php/bc as-expression count not increased, src/php/filesystem as-expression count not increased, src/php/misc as-expression count not increased, src/php/pcre as-expression count not increased, src/php/xdiff as-expression count not increased, src/php local PhpValue alias count not increased, src/php local PhpInput alias count outside _helpers not increased, src/php direct $locutus?.php?.ini reads not increased, src/php PhpValue identifier count not increased, src/php inline nullish alias count not increased, src/php Reflect.get/set count not increased, src/php globalThis identifier count not increased',
    +)
    
  • scripts/check-type-contracts-snapshot.ts+329 0 added
    @@ -0,0 +1,329 @@
    +import fs from 'node:fs'
    +import path from 'node:path'
    +import ts from 'typescript'
    +
    +type LiteralParamContract = {
    +  invalidLiteral: number | string
    +  validLiteral: number | string
    +}
    +
    +type PrimitiveBoundaryContract = {
    +  rejectBoolean: boolean
    +  rejectNumber: boolean
    +  rejectString: boolean
    +}
    +
    +const cwd = process.cwd()
    +const shouldUpdate = process.argv.includes('--update')
    +const outputPath = path.join(cwd, 'test', 'util', 'type-contracts.generated.d.ts')
    +const srcDir = path.join(cwd, 'src')
    +
    +const hasExportModifier = (node: ts.Node): boolean =>
    +  !!node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
    +
    +const getConfigPath = (): string => ts.findConfigFile(cwd, ts.sys.fileExists, 'tsconfig.json') || ''
    +const getStableHashId = (value: string): string => {
    +  let hash = 0
    +  for (const char of value) {
    +    hash = (hash * 31 + char.charCodeAt(0)) >>> 0
    +  }
    +
    +  return hash.toString(36)
    +}
    +
    +const getTypeLiteralContract = (parameterType: ts.Type): LiteralParamContract | null => {
    +  const typeMembers = parameterType.isUnion() ? parameterType.types : [parameterType]
    +
    +  const stringLiterals: string[] = []
    +  const numberLiterals: number[] = []
    +  let hasBroadString = false
    +  let hasBroadNumber = false
    +
    +  for (const member of typeMembers) {
    +    if (member.flags & ts.TypeFlags.String) {
    +      hasBroadString = true
    +      continue
    +    }
    +
    +    if (member.flags & ts.TypeFlags.Number) {
    +      hasBroadNumber = true
    +      continue
    +    }
    +
    +    if (member.isStringLiteral()) {
    +      stringLiterals.push(member.value)
    +      continue
    +    }
    +
    +    if (member.isNumberLiteral()) {
    +      numberLiterals.push(member.value)
    +    }
    +  }
    +
    +  if (!hasBroadString && stringLiterals.length > 0) {
    +    const sentinelBase = '__LOCUTUS_INVALID_LITERAL__'
    +    const invalidLiteral = stringLiterals.includes(sentinelBase) ? `${sentinelBase}_X` : sentinelBase
    +    return { validLiteral: stringLiterals[0], invalidLiteral }
    +  }
    +
    +  if (!hasBroadNumber && numberLiterals.length > 0) {
    +    const sentinelBase = -9007199254740991
    +    const invalidLiteral = numberLiterals.includes(sentinelBase) ? sentinelBase + 1 : sentinelBase
    +    return { validLiteral: numberLiterals[0], invalidLiteral }
    +  }
    +
    +  return null
    +}
    +
    +const getPrimitiveBoundaryContract = (parameterType: ts.Type): PrimitiveBoundaryContract | null => {
    +  const typeMembers = parameterType.isUnion() ? parameterType.types : [parameterType]
    +  let hasBroadBoolean = false
    +  let hasBroadNumber = false
    +  let hasBroadString = false
    +
    +  for (const member of typeMembers) {
    +    if (member.flags & ts.TypeFlags.Boolean) {
    +      hasBroadBoolean = true
    +      continue
    +    }
    +    if (member.flags & ts.TypeFlags.Number) {
    +      hasBroadNumber = true
    +      continue
    +    }
    +    if (member.flags & ts.TypeFlags.String) {
    +      hasBroadString = true
    +    }
    +  }
    +
    +  const contract: PrimitiveBoundaryContract = {
    +    rejectBoolean: !hasBroadBoolean && (hasBroadNumber || hasBroadString),
    +    rejectNumber: !hasBroadNumber && (hasBroadBoolean || hasBroadString),
    +    rejectString: !hasBroadString && (hasBroadBoolean || hasBroadNumber),
    +  }
    +
    +  if (!contract.rejectBoolean && !contract.rejectNumber && !contract.rejectString) {
    +    return null
    +  }
    +
    +  return contract
    +}
    +
    +const configPath = getConfigPath()
    +if (!configPath) {
    +  console.error('Unable to find tsconfig.json for type contract generation.')
    +  process.exit(1)
    +}
    +
    +const parsedConfig = ts.getParsedCommandLineOfConfigFile(
    +  configPath,
    +  {},
    +  {
    +    ...ts.sys,
    +    onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
    +      const host = {
    +        getCanonicalFileName: (fileName: string) => fileName,
    +        getCurrentDirectory: () => cwd,
    +        getNewLine: () => '\n',
    +      }
    +      console.error(ts.formatDiagnostic(diagnostic, host))
    +      process.exit(1)
    +    },
    +  },
    +)
    +
    +if (!parsedConfig) {
    +  console.error('Failed to parse tsconfig.json for type contract generation.')
    +  process.exit(1)
    +}
    +
    +const program = ts.createProgram({
    +  rootNames: parsedConfig.fileNames,
    +  options: parsedConfig.options,
    +})
    +const checker = program.getTypeChecker()
    +
    +const importEntries: Array<{ importPath: string; line: string }> = []
    +const contractLines: string[] = []
    +const sourceFiles = program
    +  .getSourceFiles()
    +  .filter((sourceFile) => !sourceFile.isDeclarationFile)
    +  .filter((sourceFile) => sourceFile.fileName.startsWith(srcDir))
    +  .filter((sourceFile) => !sourceFile.fileName.includes(`${path.sep}src${path.sep}_util${path.sep}`))
    +  .sort((left, right) => (left.fileName < right.fileName ? -1 : left.fileName > right.fileName ? 1 : 0))
    +
    +let functionIndex = 0
    +const usedModuleAliases = new Set<string>()
    +
    +for (const sourceFile of sourceFiles) {
    +  const relativeSourcePath = path.relative(cwd, sourceFile.fileName).replaceAll(path.sep, '/')
    +  const importPath = `../../${relativeSourcePath}`
    +  const functionDeclarations = sourceFile.statements.filter(
    +    (node): node is ts.FunctionDeclaration =>
    +      ts.isFunctionDeclaration(node) && hasExportModifier(node) && !!node.name && !!node.body,
    +  )
    +
    +  if (functionDeclarations.length === 0) {
    +    continue
    +  }
    +
    +  const baseAlias = `m_${getStableHashId(relativeSourcePath)}`
    +  let moduleAlias = baseAlias
    +  let aliasSuffix = 1
    +  while (usedModuleAliases.has(moduleAlias)) {
    +    moduleAlias = `${baseAlias}_${aliasSuffix}`
    +    aliasSuffix += 1
    +  }
    +  usedModuleAliases.add(moduleAlias)
    +  importEntries.push({
    +    importPath,
    +    line: `import type * as ${moduleAlias} from '${importPath}'`,
    +  })
    +
    +  for (const node of functionDeclarations) {
    +    if (!node.name) {
    +      continue
    +    }
    +
    +    const functionName = node.name.text
    +    const contractId = String(functionIndex).padStart(4, '0')
    +    const contractPrefix = `_TypeContract_${contractId}`
    +    functionIndex += 1
    +
    +    const functionRef = `${moduleAlias}.${functionName}`
    +    contractLines.push(`type ${contractPrefix}_ReturnNotAny = ExpectFalse<IsAny<ReturnType<typeof ${functionRef}>>>`)
    +
    +    const functionType = checker.getTypeAtLocation(node.name)
    +    const callSignatures = checker.getSignaturesOfType(functionType, ts.SignatureKind.Call)
    +    const primarySignature = callSignatures[callSignatures.length - 1]
    +    if (!primarySignature) {
    +      continue
    +    }
    +
    +    primarySignature.parameters.forEach((parameterSymbol, parameterIndex) => {
    +      const paramRef = `Parameters<typeof ${functionRef}>[${parameterIndex}]`
    +      contractLines.push(`type ${contractPrefix}_Param${parameterIndex}_NotAny = ExpectFalse<IsAny<${paramRef}>>`)
    +
    +      const declaration = parameterSymbol.valueDeclaration
    +      if (declaration && ts.isParameter(declaration) && !declaration.dotDotDotToken) {
    +        const isOptionalParam = Boolean(declaration.questionToken || declaration.initializer)
    +        if (isOptionalParam) {
    +          contractLines.push(
    +            `type ${contractPrefix}_Param${parameterIndex}_OptionalAcceptsUndefined = ExpectTrue<IsAssignable<undefined, ${paramRef}>>`,
    +          )
    +        }
    +      }
    +
    +      const parameterType = checker.getTypeOfSymbolAtLocation(parameterSymbol, node.name)
    +      const literalContract = getTypeLiteralContract(parameterType)
    +      if (literalContract) {
    +        const validLiteralText =
    +          typeof literalContract.validLiteral === 'string'
    +            ? `'${literalContract.validLiteral.replaceAll("'", "\\'")}'`
    +            : `${literalContract.validLiteral}`
    +        const invalidLiteralText =
    +          typeof literalContract.invalidLiteral === 'string'
    +            ? `'${literalContract.invalidLiteral.replaceAll("'", "\\'")}'`
    +            : `${literalContract.invalidLiteral}`
    +
    +        contractLines.push(
    +          `type ${contractPrefix}_Param${parameterIndex}_AcceptsKnownLiteral = ExpectTrue<IsAssignable<${validLiteralText}, ${paramRef}>>`,
    +        )
    +        contractLines.push(
    +          `type ${contractPrefix}_Param${parameterIndex}_RejectsInvalidLiteral = ExpectFalse<IsAssignable<${invalidLiteralText}, ${paramRef}>>`,
    +        )
    +      }
    +
    +      const primitiveContract = getPrimitiveBoundaryContract(parameterType)
    +      if (!primitiveContract) {
    +        return
    +      }
    +
    +      if (primitiveContract.rejectString) {
    +        contractLines.push(
    +          `type ${contractPrefix}_Param${parameterIndex}_RejectsString = ExpectFalse<IsAssignable<'__LOCUTUS_INVALID_STRING__', ${paramRef}>>`,
    +        )
    +      }
    +      if (primitiveContract.rejectNumber) {
    +        contractLines.push(
    +          `type ${contractPrefix}_Param${parameterIndex}_RejectsNumber = ExpectFalse<IsAssignable<123456789, ${paramRef}>>`,
    +        )
    +      }
    +      if (primitiveContract.rejectBoolean) {
    +        contractLines.push(
    +          `type ${contractPrefix}_Param${parameterIndex}_RejectsBoolean = ExpectFalse<IsAssignable<true, ${paramRef}>>`,
    +        )
    +      }
    +    })
    +  }
    +}
    +
    +const header = [
    +  '// This file is generated by scripts/check-type-contracts-snapshot.ts',
    +  '// Do not edit manually. Run: corepack yarn fix:type:contracts',
    +  '',
    +  'type IsAny<T> = 0 extends 1 & T ? true : false',
    +  'type IsAssignable<From, To> = [From] extends [To] ? true : false',
    +  'type ExpectTrue<T extends true> = T',
    +  'type ExpectFalse<T extends false> = T',
    +  '',
    +]
    +const sortedImportLines = [...importEntries]
    +  .sort((left, right) => {
    +    const leftKey = left.importPath.toLowerCase()
    +    const rightKey = right.importPath.toLowerCase()
    +    if (leftKey < rightKey) {
    +      return -1
    +    }
    +    if (leftKey > rightKey) {
    +      return 1
    +    }
    +
    +    return left.importPath < right.importPath ? -1 : left.importPath > right.importPath ? 1 : 0
    +  })
    +  .map((entry) => entry.line)
    +const content = [...header, ...sortedImportLines, '', ...contractLines, ''].join('\n')
    +const normalizeForComparison = (value: string): string => {
    +  const lines = value
    +    .split('\n')
    +    .map((line) => line.trim())
    +    .filter((line) => line.length > 0)
    +  const importLines = lines.filter((line) => line.startsWith('import type * as '))
    +  const otherLines = lines.filter((line) => !line.startsWith('import type * as '))
    +  const normalizedImports = importLines.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0))
    +  return [...normalizedImports, ...otherLines].join('\n').replaceAll(/\s+/g, ' ').trim()
    +}
    +
    +if (shouldUpdate) {
    +  fs.writeFileSync(outputPath, content)
    +  console.log(`Updated type contract snapshot at ${path.relative(cwd, outputPath)}`)
    +  process.exit(0)
    +}
    +
    +if (!fs.existsSync(outputPath)) {
    +  console.error(`Type contract snapshot file missing: ${path.relative(cwd, outputPath)}`)
    +  console.error('Run: corepack yarn fix:type:contracts')
    +  process.exit(1)
    +}
    +
    +const expected = fs.readFileSync(outputPath, 'utf8')
    +if (normalizeForComparison(expected) !== normalizeForComparison(content)) {
    +  const expectedLines = expected.split('\n')
    +  const currentLines = content.split('\n')
    +  let firstDiffIndex = 0
    +
    +  while (
    +    firstDiffIndex < expectedLines.length &&
    +    firstDiffIndex < currentLines.length &&
    +    expectedLines[firstDiffIndex] === currentLines[firstDiffIndex]
    +  ) {
    +    firstDiffIndex += 1
    +  }
    +
    +  console.error(`Type contract snapshot mismatch at line ${firstDiffIndex + 1}`)
    +  console.error(`Expected: ${expectedLines[firstDiffIndex] ?? '<EOF>'}`)
    +  console.error(`Current:  ${currentLines[firstDiffIndex] ?? '<EOF>'}`)
    +  console.error('If this change is intentional, run: corepack yarn fix:type:contracts')
    +  process.exit(1)
    +}
    +
    +console.log(`Type contract snapshot is up to date (${path.relative(cwd, outputPath)})`)
    
  • scripts/compare-definitelytyped-signatures.ts+486 0 added
    @@ -0,0 +1,486 @@
    +import fs from 'node:fs'
    +import path from 'node:path'
    +import ts from 'typescript'
    +
    +type SignatureSet = {
    +  modulePath: string
    +  exportName: string
    +  signatures: string[]
    +}
    +
    +type SignatureMetrics = {
    +  anyCount: number
    +  unknownCount: number
    +  functionCount: number
    +  objectCount: number
    +  optionalParamCount: number
    +  restAnyCount: number
    +}
    +
    +type DifferenceKind = 'ours-stricter' | 'dt-stricter' | 'inconclusive'
    +
    +type Difference = {
    +  key: string
    +  modulePath: string
    +  exportName: string
    +  ours: string[]
    +  dt: string[]
    +  oursMetrics: SignatureMetrics
    +  dtMetrics: SignatureMetrics
    +  kind: DifferenceKind
    +}
    +
    +type NpmRegistryMetadata = {
    +  'dist-tags'?: {
    +    latest?: string
    +  }
    +}
    +
    +const cwd = process.cwd()
    +const srcDir = path.join(cwd, 'src')
    +const customDtIndexArg = process.argv.find((arg) => arg.startsWith('--dt-index='))
    +const maxArg = process.argv.find((arg) => arg.startsWith('--max='))
    +const maxDiffOutput = maxArg ? Number.parseInt(maxArg.slice('--max='.length), 10) : 25
    +const reportPathArg = process.argv.find((arg) => arg.startsWith('--report='))
    +
    +const normalizeWhitespace = (value: string): string => value.replace(/\s+/g, ' ').trim()
    +
    +const sortUnique = (items: string[]): string[] => [...new Set(items)].sort()
    +
    +const createMetric = (signatures: string[]): SignatureMetrics => {
    +  const combined = signatures.join(' ')
    +
    +  return {
    +    anyCount: (combined.match(/\bany\b/g) ?? []).length,
    +    unknownCount: (combined.match(/\bunknown\b/g) ?? []).length,
    +    functionCount: (combined.match(/\bFunction\b/g) ?? []).length,
    +    objectCount: (combined.match(/:\s*object\b/g) ?? []).length,
    +    optionalParamCount: (combined.match(/\?:/g) ?? []).length,
    +    restAnyCount: (combined.match(/\.\.\.\w+:\s*any\[\]/g) ?? []).length,
    +  }
    +}
    +
    +const metricPenalty = (metrics: SignatureMetrics): number =>
    +  metrics.anyCount * 100 +
    +  metrics.unknownCount * 25 +
    +  metrics.restAnyCount * 25 +
    +  metrics.functionCount * 15 +
    +  metrics.objectCount * 10 +
    +  metrics.optionalParamCount
    +
    +const classifyDifference = (ours: SignatureMetrics, dt: SignatureMetrics): DifferenceKind => {
    +  const oursPenalty = metricPenalty(ours)
    +  const dtPenalty = metricPenalty(dt)
    +
    +  if (oursPenalty < dtPenalty) {
    +    return 'ours-stricter'
    +  }
    +
    +  if (oursPenalty > dtPenalty) {
    +    return 'dt-stricter'
    +  }
    +
    +  return 'inconclusive'
    +}
    +
    +const getTsConfigPath = (): string => ts.findConfigFile(cwd, ts.sys.fileExists, 'tsconfig.json') || ''
    +
    +const collectLocalSignatures = (): Map<string, SignatureSet> => {
    +  const configPath = getTsConfigPath()
    +  if (!configPath) {
    +    throw new Error('Unable to find tsconfig.json')
    +  }
    +
    +  const parsedConfig = ts.getParsedCommandLineOfConfigFile(
    +    configPath,
    +    {},
    +    {
    +      ...ts.sys,
    +      onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
    +        const host = {
    +          getCanonicalFileName: (fileName: string) => fileName,
    +          getCurrentDirectory: () => cwd,
    +          getNewLine: () => '\n',
    +        }
    +        throw new Error(ts.formatDiagnostic(diagnostic, host))
    +      },
    +    },
    +  )
    +
    +  if (!parsedConfig) {
    +    throw new Error('Failed to parse tsconfig.json')
    +  }
    +
    +  const program = ts.createProgram({
    +    rootNames: parsedConfig.fileNames,
    +    options: parsedConfig.options,
    +  })
    +  const checker = program.getTypeChecker()
    +  const results = new Map<string, SignatureSet>()
    +
    +  for (const sourceFile of program.getSourceFiles()) {
    +    if (sourceFile.isDeclarationFile) {
    +      continue
    +    }
    +    if (!sourceFile.fileName.startsWith(srcDir)) {
    +      continue
    +    }
    +    if (sourceFile.fileName.includes(`${path.sep}src${path.sep}_util${path.sep}`)) {
    +      continue
    +    }
    +
    +    const moduleSymbol = checker.getSymbolAtLocation(sourceFile)
    +    if (!moduleSymbol) {
    +      continue
    +    }
    +
    +    const modulePath = `locutus/${path
    +      .relative(srcDir, sourceFile.fileName)
    +      .replaceAll(path.sep, '/')
    +      .replace(/\.ts$/, '')}`
    +
    +    for (const exportSymbol of checker.getExportsOfModule(moduleSymbol)) {
    +      const declarations = exportSymbol.getDeclarations()
    +      if (!declarations || declarations.length === 0) {
    +        continue
    +      }
    +
    +      const declarationForType = declarations[declarations.length - 1]
    +      const exportType = checker.getTypeOfSymbolAtLocation(exportSymbol, declarationForType)
    +      const signatures = checker.getSignaturesOfType(exportType, ts.SignatureKind.Call)
    +      if (signatures.length === 0) {
    +        continue
    +      }
    +
    +      const signatureTexts = sortUnique(
    +        signatures.map((signature) =>
    +          normalizeWhitespace(
    +            checker.signatureToString(signature, declarationForType, ts.TypeFormatFlags.NoTruncation),
    +          ),
    +        ),
    +      )
    +
    +      const exportName = exportSymbol.getName()
    +      const key = `${modulePath}::${exportName}`
    +      results.set(key, {
    +        modulePath,
    +        exportName,
    +        signatures: signatureTexts,
    +      })
    +    }
    +  }
    +
    +  return results
    +}
    +
    +const readLocalDtIndex = (): string | null => {
    +  if (customDtIndexArg) {
    +    const customPath = customDtIndexArg.slice('--dt-index='.length)
    +    const resolvedPath = path.resolve(cwd, customPath)
    +    if (!fs.existsSync(resolvedPath)) {
    +      throw new Error(`--dt-index path does not exist: ${resolvedPath}`)
    +    }
    +    return fs.readFileSync(resolvedPath, 'utf8')
    +  }
    +
    +  const bundledPath = path.join(cwd, 'node_modules', '@types', 'locutus', 'index.d.ts')
    +  if (fs.existsSync(bundledPath)) {
    +    return fs.readFileSync(bundledPath, 'utf8')
    +  }
    +
    +  return null
    +}
    +
    +const fetchLatestDtIndex = async (): Promise<{ version: string; body: string }> => {
    +  const registryResponse = await fetch('https://registry.npmjs.org/@types/locutus')
    +  if (!registryResponse.ok) {
    +    throw new Error(`Failed to read npm metadata for @types/locutus: HTTP ${registryResponse.status}`)
    +  }
    +
    +  const metadata = (await registryResponse.json()) as NpmRegistryMetadata
    +  const latest = metadata['dist-tags']?.latest
    +  if (!latest) {
    +    throw new Error('Could not determine latest @types/locutus version from npm metadata.')
    +  }
    +
    +  const indexResponse = await fetch(`https://unpkg.com/@types/locutus@${latest}/index.d.ts`)
    +  if (!indexResponse.ok) {
    +    throw new Error(`Failed to fetch @types/locutus@${latest} index.d.ts: HTTP ${indexResponse.status}`)
    +  }
    +
    +  const body = await indexResponse.text()
    +  return { version: latest, body }
    +}
    +
    +const getModuleBlock = (moduleDeclaration: ts.ModuleDeclaration): ts.ModuleBlock | null => {
    +  let current: ts.ModuleBody | undefined = moduleDeclaration.body
    +  while (current && ts.isModuleDeclaration(current)) {
    +    current = current.body
    +  }
    +
    +  if (current && ts.isModuleBlock(current)) {
    +    return current
    +  }
    +
    +  return null
    +}
    +
    +const formatDtSignature = (sourceFile: ts.SourceFile, declaration: ts.FunctionDeclaration): string => {
    +  const typeParameters = declaration.typeParameters
    +    ? `<${declaration.typeParameters.map((typeParameter) => normalizeWhitespace(typeParameter.getText(sourceFile))).join(', ')}>`
    +    : ''
    +
    +  const parameters = declaration.parameters
    +    .map((parameter) => {
    +      const name = parameter.name.getText(sourceFile)
    +      const prefix = parameter.dotDotDotToken ? '...' : ''
    +      const optional = parameter.questionToken ? '?' : ''
    +      const typeText = parameter.type ? normalizeWhitespace(parameter.type.getText(sourceFile)) : 'any'
    +      return `${prefix}${name}${optional}: ${typeText}`
    +    })
    +    .join(', ')
    +
    +  const returnType = declaration.type ? normalizeWhitespace(declaration.type.getText(sourceFile)) : 'any'
    +  return `${typeParameters}(${parameters}): ${returnType}`
    +}
    +
    +const collectDtSignatures = (dtSourceText: string): Map<string, SignatureSet> => {
    +  const sourceFile = ts.createSourceFile('definitelytyped-index.d.ts', dtSourceText, ts.ScriptTarget.Latest, true)
    +  const results = new Map<string, SignatureSet>()
    +
    +  for (const statement of sourceFile.statements) {
    +    if (!ts.isModuleDeclaration(statement)) {
    +      continue
    +    }
    +    if (!ts.isStringLiteral(statement.name)) {
    +      continue
    +    }
    +
    +    const modulePath = statement.name.text
    +    const moduleBlock = getModuleBlock(statement)
    +    if (!moduleBlock) {
    +      continue
    +    }
    +
    +    for (const moduleStatement of moduleBlock.statements) {
    +      if (!ts.isFunctionDeclaration(moduleStatement) || !moduleStatement.name) {
    +        continue
    +      }
    +
    +      const exportName = moduleStatement.name.text
    +      const key = `${modulePath}::${exportName}`
    +      const previous = results.get(key)
    +      const signatureText = formatDtSignature(sourceFile, moduleStatement)
    +      const signatures = previous ? sortUnique([...previous.signatures, signatureText]) : [signatureText]
    +
    +      results.set(key, {
    +        modulePath,
    +        exportName,
    +        signatures,
    +      })
    +    }
    +  }
    +
    +  return results
    +}
    +
    +const toComparableSignature = (signature: string): string =>
    +  normalizeWhitespace(signature)
    +    .replace(/<[^>]*>/g, '')
    +    .replace(/\s+/g, ' ')
    +    .trim()
    +
    +const compareSignatureSets = (left: string[], right: string[]): boolean => {
    +  const normalizedLeft = sortUnique(left.map(toComparableSignature))
    +  const normalizedRight = sortUnique(right.map(toComparableSignature))
    +  if (normalizedLeft.length !== normalizedRight.length) {
    +    return false
    +  }
    +
    +  for (let index = 0; index < normalizedLeft.length; index += 1) {
    +    if (normalizedLeft[index] !== normalizedRight[index]) {
    +      return false
    +    }
    +  }
    +
    +  return true
    +}
    +
    +const toReport = (
    +  differences: Difference[],
    +  missingInLocal: SignatureSet[],
    +  missingInDt: SignatureSet[],
    +  equalCount: number,
    +  sharedCount: number,
    +  dtSourceLabel: string,
    +): string => {
    +  const lines: string[] = []
    +  lines.push('# DefinitelyTyped Signature Comparison')
    +  lines.push('')
    +  lines.push(`- Compared against: ${dtSourceLabel}`)
    +  lines.push(`- Shared exports compared: ${sharedCount}`)
    +  lines.push(`- Signature-identical exports: ${equalCount}`)
    +  lines.push(`- Different exports: ${differences.length}`)
    +  lines.push(`- Missing in local types: ${missingInLocal.length}`)
    +  lines.push(`- Missing in DefinitelyTyped: ${missingInDt.length}`)
    +  lines.push('')
    +
    +  const oursStricter = differences.filter((difference) => difference.kind === 'ours-stricter')
    +  const dtStricter = differences.filter((difference) => difference.kind === 'dt-stricter')
    +  const inconclusive = differences.filter((difference) => difference.kind === 'inconclusive')
    +
    +  lines.push('## Difference Breakdown')
    +  lines.push('')
    +  lines.push(`- Ours stricter: ${oursStricter.length}`)
    +  lines.push(`- DT stricter: ${dtStricter.length}`)
    +  lines.push(`- Inconclusive: ${inconclusive.length}`)
    +  lines.push('')
    +
    +  const appendDiffGroup = (title: string, list: Difference[]): void => {
    +    lines.push(`## ${title}`)
    +    lines.push('')
    +    if (list.length === 0) {
    +      lines.push('- None')
    +      lines.push('')
    +      return
    +    }
    +    for (const item of list) {
    +      lines.push(`- \`${item.modulePath}\` / \`${item.exportName}\``)
    +      lines.push(`  ours: ${item.ours.join(' || ')}`)
    +      lines.push(`  dt:   ${item.dt.join(' || ')}`)
    +    }
    +    lines.push('')
    +  }
    +
    +  appendDiffGroup('DT Stricter (Review First)', dtStricter)
    +  appendDiffGroup('Inconclusive', inconclusive)
    +
    +  lines.push('## Missing In Local')
    +  lines.push('')
    +  if (missingInLocal.length === 0) {
    +    lines.push('- None')
    +  } else {
    +    for (const item of missingInLocal) {
    +      lines.push(`- \`${item.modulePath}\` / \`${item.exportName}\``)
    +    }
    +  }
    +  lines.push('')
    +
    +  lines.push('## Missing In DefinitelyTyped')
    +  lines.push('')
    +  if (missingInDt.length === 0) {
    +    lines.push('- None')
    +  } else {
    +    for (const item of missingInDt) {
    +      lines.push(`- \`${item.modulePath}\` / \`${item.exportName}\``)
    +    }
    +  }
    +  lines.push('')
    +
    +  return lines.join('\n')
    +}
    +
    +const printDifferenceGroup = (title: string, list: Difference[]): void => {
    +  console.log(`\n${title}: ${list.length}`)
    +  const limited = list.slice(0, Math.max(0, maxDiffOutput))
    +  for (const item of limited) {
    +    console.log(`- ${item.modulePath} :: ${item.exportName}`)
    +    console.log(`  ours: ${item.ours.join(' || ')}`)
    +    console.log(`  dt:   ${item.dt.join(' || ')}`)
    +  }
    +  if (list.length > limited.length) {
    +    console.log(`  ... and ${list.length - limited.length} more`)
    +  }
    +}
    +
    +const main = async (): Promise<void> => {
    +  const localSignatures = collectLocalSignatures()
    +  const localDtIndex = readLocalDtIndex()
    +
    +  let dtSourceLabel = 'local node_modules/@types/locutus'
    +  let dtSourceText = localDtIndex
    +
    +  if (!dtSourceText) {
    +    const fetched = await fetchLatestDtIndex()
    +    dtSourceLabel = `@types/locutus@${fetched.version} (fetched from npm/unpkg)`
    +    dtSourceText = fetched.body
    +  }
    +
    +  const dtSignatures = collectDtSignatures(dtSourceText)
    +
    +  const localKeys = new Set(localSignatures.keys())
    +  const dtKeys = new Set(dtSignatures.keys())
    +  const sharedKeys = [...localKeys].filter((key) => dtKeys.has(key)).sort()
    +
    +  let equalCount = 0
    +  const differences: Difference[] = []
    +
    +  for (const key of sharedKeys) {
    +    const ours = localSignatures.get(key)
    +    const dt = dtSignatures.get(key)
    +    if (!ours || !dt) {
    +      continue
    +    }
    +
    +    if (compareSignatureSets(ours.signatures, dt.signatures)) {
    +      equalCount += 1
    +      continue
    +    }
    +
    +    const oursMetrics = createMetric(ours.signatures)
    +    const dtMetrics = createMetric(dt.signatures)
    +
    +    differences.push({
    +      key,
    +      modulePath: ours.modulePath,
    +      exportName: ours.exportName,
    +      ours: ours.signatures,
    +      dt: dt.signatures,
    +      oursMetrics,
    +      dtMetrics,
    +      kind: classifyDifference(oursMetrics, dtMetrics),
    +    })
    +  }
    +
    +  const missingInLocal = [...dtKeys]
    +    .filter((key) => !localKeys.has(key))
    +    .sort()
    +    .map((key) => dtSignatures.get(key))
    +    .filter((value): value is SignatureSet => Boolean(value))
    +
    +  const missingInDt = [...localKeys]
    +    .filter((key) => !dtKeys.has(key))
    +    .sort()
    +    .map((key) => localSignatures.get(key))
    +    .filter((value): value is SignatureSet => Boolean(value))
    +
    +  const oursStricter = differences.filter((difference) => difference.kind === 'ours-stricter')
    +  const dtStricter = differences.filter((difference) => difference.kind === 'dt-stricter')
    +  const inconclusive = differences.filter((difference) => difference.kind === 'inconclusive')
    +
    +  console.log(`Compared against ${dtSourceLabel}`)
    +  console.log(`Shared exports: ${sharedKeys.length}`)
    +  console.log(`Signature-identical: ${equalCount}`)
    +  console.log(`Different signatures: ${differences.length}`)
    +  console.log(`Missing in local: ${missingInLocal.length}`)
    +  console.log(`Missing in DefinitelyTyped: ${missingInDt.length}`)
    +  console.log(`Likely ours stricter: ${oursStricter.length}`)
    +  console.log(`Likely DT stricter: ${dtStricter.length}`)
    +  console.log(`Inconclusive: ${inconclusive.length}`)
    +
    +  printDifferenceGroup('Likely DT stricter', dtStricter)
    +  printDifferenceGroup('Inconclusive', inconclusive)
    +
    +  if (reportPathArg) {
    +    const reportPath = path.resolve(cwd, reportPathArg.slice('--report='.length))
    +    const reportBody = toReport(differences, missingInLocal, missingInDt, equalCount, sharedKeys.length, dtSourceLabel)
    +    fs.writeFileSync(reportPath, reportBody)
    +    console.log(`\nWrote report: ${path.relative(cwd, reportPath)}`)
    +  }
    +}
    +
    +main().catch((error) => {
    +  const message = error instanceof Error ? error.message : String(error)
    +  console.error(`compare-definitelytyped-signatures failed: ${message}`)
    +  process.exit(1)
    +})
    
  • scripts/fix-cjs-exports.ts+94 0 added
    @@ -0,0 +1,94 @@
    +/**
    + * Post-process dist output for publication while preserving named exports.
    + *
    + * We intentionally do not collapse CJS modules into direct callable defaults.
    + * Consumers should use named imports/exports in both ESM and CJS:
    + *   - ESM: import { fn } from '...'
    + *   - CJS: const { fn } = require('...')
    + */
    +
    +import fs from 'node:fs'
    +import path from 'node:path'
    +
    +const distDir = process.argv[2] || 'dist'
    +const writeGolangIndexCompatShim = (rootDir: string): void => {
    +  const cjsStringsDir = path.join(rootDir, 'golang', 'strings')
    +  const cjsIndex2JsPath = path.join(cjsStringsDir, 'Index2.js')
    +  const cjsIndexJsPath = path.join(cjsStringsDir, 'Index.js')
    +  const cjsIndex2DtsPath = path.join(cjsStringsDir, 'Index2.d.ts')
    +  const cjsIndexDtsPath = path.join(cjsStringsDir, 'Index.d.ts')
    +
    +  if (fs.existsSync(cjsIndex2JsPath) && !fs.existsSync(cjsIndexJsPath)) {
    +    const cjsShim =
    +      "// Backward-compatible deep import shim for 'locutus/golang/strings/Index'.\n" +
    +      "const mod = require('./Index2.js');\n" +
    +      'exports.Index = mod.Index;\n'
    +    fs.writeFileSync(cjsIndexJsPath, cjsShim, 'utf-8')
    +  }
    +
    +  if (fs.existsSync(cjsIndex2DtsPath) && !fs.existsSync(cjsIndexDtsPath)) {
    +    fs.writeFileSync(cjsIndexDtsPath, "export { Index } from './Index2'\n", 'utf-8')
    +  }
    +
    +  const esmStringsDir = path.join(rootDir, 'esm', 'golang', 'strings')
    +  const esmIndex2JsPath = path.join(esmStringsDir, 'Index2.js')
    +  const esmIndexJsPath = path.join(esmStringsDir, 'Index.js')
    +  if (fs.existsSync(esmIndex2JsPath) && !fs.existsSync(esmIndexJsPath)) {
    +    const esmShim =
    +      "// Backward-compatible deep import shim for 'locutus/golang/strings/Index'.\n" +
    +      "export { Index } from './Index2.js'\n"
    +    fs.writeFileSync(esmIndexJsPath, esmShim, 'utf-8')
    +  }
    +}
    +
    +const writeEsmPackageJson = (rootDir: string): void => {
    +  const esmDir = path.join(rootDir, 'esm')
    +  if (!fs.existsSync(esmDir)) {
    +    return
    +  }
    +
    +  const esmPackageJsonPath = path.join(esmDir, 'package.json')
    +  const esmPackageJson = { type: 'module' }
    +  fs.writeFileSync(esmPackageJsonPath, `${JSON.stringify(esmPackageJson, null, 2)}\n`, 'utf-8')
    +}
    +
    +const patchDistPackageJson = (rootDir: string): void => {
    +  const packageJsonPath = path.join(rootDir, 'package.json')
    +  if (!fs.existsSync(packageJsonPath)) {
    +    return
    +  }
    +
    +  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as {
    +    exports?: Record<string, unknown>
    +    type?: string
    +    types?: string
    +    module?: string
    +    main?: string
    +  }
    +
    +  packageJson.type = 'commonjs'
    +  packageJson.main = './index.js'
    +  packageJson.module = './esm/index.js'
    +  packageJson.types = './index.d.ts'
    +  packageJson.exports = {
    +    '.': {
    +      types: './index.d.ts',
    +      import: './esm/index.js',
    +      require: './index.js',
    +      default: './index.js',
    +    },
    +    './*': {
    +      types: './*.d.ts',
    +      import: './esm/*.js',
    +      require: './*.js',
    +      default: './*.js',
    +    },
    +    './package.json': './package.json',
    +  }
    +
    +  fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf-8')
    +}
    +
    +writeGolangIndexCompatShim(distDir)
    +writeEsmPackageJson(distDir)
    +patchDistPackageJson(distDir)
    
  • src/awk/builtin/cos.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function cos(x) {
    -  //      discuss at: https://locutus.io/awk/cos/
    -  // parity verified: GNU AWK 5.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: cos(0)
    -  //       returns 1: 1
    -  //       example 2: cos(1)
    -  //       returns 2: 0.5403023058681398
    -
    -  return Math.cos(x)
    -}
    
  • src/awk/builtin/cos.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function cos(x: number): number {
    +  //      discuss at: https://locutus.io/awk/cos/
    +  // parity verified: GNU AWK 5.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: cos(0)
    +  //       returns 1: 1
    +  //       example 2: cos(1)
    +  //       returns 2: 0.5403023058681398
    +
    +  return Math.cos(x)
    +}
    
  • src/awk/builtin/exp.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function exp(x) {
    -  //      discuss at: https://locutus.io/awk/exp/
    -  // parity verified: GNU AWK 5.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: exp(0)
    -  //       returns 1: 1
    -  //       example 2: exp(1)
    -  //       returns 2: 2.718281828459045
    -
    -  return Math.exp(x)
    -}
    
  • src/awk/builtin/exp.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function exp(x: number): number {
    +  //      discuss at: https://locutus.io/awk/exp/
    +  // parity verified: GNU AWK 5.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: exp(0)
    +  //       returns 1: 1
    +  //       example 2: exp(1)
    +  //       returns 2: 2.718281828459045
    +
    +  return Math.exp(x)
    +}
    
  • src/awk/builtin/index.js+0 10 removed
    @@ -1,10 +0,0 @@
    -module.exports.cos = require('./cos')
    -module.exports.exp = require('./exp')
    -module.exports.int = require('./int')
    -module.exports.length = require('./length')
    -module.exports.log = require('./log')
    -module.exports.sin = require('./sin')
    -module.exports.sqrt = require('./sqrt')
    -module.exports.substr = require('./substr')
    -module.exports.tolower = require('./tolower')
    -module.exports.toupper = require('./toupper')
    
  • src/awk/builtin/index.ts+10 0 added
    @@ -0,0 +1,10 @@
    +export { cos } from './cos.ts'
    +export { exp } from './exp.ts'
    +export { int } from './int.ts'
    +export { length } from './length.ts'
    +export { log } from './log.ts'
    +export { sin } from './sin.ts'
    +export { sqrt } from './sqrt.ts'
    +export { substr } from './substr.ts'
    +export { tolower } from './tolower.ts'
    +export { toupper } from './toupper.ts'
    
  • src/awk/builtin/int.js+0 13 removed
    @@ -1,13 +0,0 @@
    -module.exports = function int(x) {
    -  //      discuss at: https://locutus.io/awk/int/
    -  // parity verified: GNU AWK 5.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: int(4.7)
    -  //       returns 1: 4
    -  //       example 2: int(-4.7)
    -  //       returns 2: -4
    -  //       example 3: int(3)
    -  //       returns 3: 3
    -
    -  return Math.trunc(x)
    -}
    
  • src/awk/builtin/int.ts+13 0 added
    @@ -0,0 +1,13 @@
    +export function int(x: number): number {
    +  //      discuss at: https://locutus.io/awk/int/
    +  // parity verified: GNU AWK 5.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: int(4.7)
    +  //       returns 1: 4
    +  //       example 2: int(-4.7)
    +  //       returns 2: -4
    +  //       example 3: int(3)
    +  //       returns 3: 3
    +
    +  return Math.trunc(x)
    +}
    
  • src/awk/builtin/length.js+0 16 removed
    @@ -1,16 +0,0 @@
    -module.exports = function length(s) {
    -  //      discuss at: https://locutus.io/awk/length/
    -  // parity verified: GNU AWK 5.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: length('hello')
    -  //       returns 1: 5
    -  //       example 2: length('')
    -  //       returns 2: 0
    -  //       example 3: length('test string')
    -  //       returns 3: 11
    -
    -  if (s === undefined || s === null) {
    -    return 0
    -  }
    -  return String(s).length
    -}
    
  • src/awk/builtin/length.ts+16 0 added
    @@ -0,0 +1,16 @@
    +export function length(s: string): number {
    +  //      discuss at: https://locutus.io/awk/length/
    +  // parity verified: GNU AWK 5.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: length('hello')
    +  //       returns 1: 5
    +  //       example 2: length('')
    +  //       returns 2: 0
    +  //       example 3: length('test string')
    +  //       returns 3: 11
    +
    +  if (s === undefined || s === null) {
    +    return 0
    +  }
    +  return String(s).length
    +}
    
  • src/awk/builtin/log.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function log(x) {
    -  //      discuss at: https://locutus.io/awk/log/
    -  // parity verified: GNU AWK 5.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: log(1)
    -  //       returns 1: 0
    -  //       example 2: log(2.718281828459045)
    -  //       returns 2: 1
    -
    -  return Math.log(x)
    -}
    
  • src/awk/builtin/log.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function log(x: number): number {
    +  //      discuss at: https://locutus.io/awk/log/
    +  // parity verified: GNU AWK 5.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: log(1)
    +  //       returns 1: 0
    +  //       example 2: log(2.718281828459045)
    +  //       returns 2: 1
    +
    +  return Math.log(x)
    +}
    
  • src/awk/builtin/sin.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function sin(x) {
    -  //      discuss at: https://locutus.io/awk/sin/
    -  // parity verified: GNU AWK 5.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: sin(0)
    -  //       returns 1: 0
    -  //       example 2: sin(1)
    -  //       returns 2: 0.8414709848078965
    -
    -  return Math.sin(x)
    -}
    
  • src/awk/builtin/sin.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function sin(x: number): number {
    +  //      discuss at: https://locutus.io/awk/sin/
    +  // parity verified: GNU AWK 5.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: sin(0)
    +  //       returns 1: 0
    +  //       example 2: sin(1)
    +  //       returns 2: 0.8414709848078965
    +
    +  return Math.sin(x)
    +}
    
  • src/awk/builtin/sqrt.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function sqrt(x) {
    -  //      discuss at: https://locutus.io/awk/sqrt/
    -  // parity verified: GNU AWK 5.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: sqrt(16)
    -  //       returns 1: 4
    -  //       example 2: sqrt(2)
    -  //       returns 2: 1.4142135623730951
    -
    -  return Math.sqrt(x)
    -}
    
  • src/awk/builtin/sqrt.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function sqrt(x: number): number {
    +  //      discuss at: https://locutus.io/awk/sqrt/
    +  // parity verified: GNU AWK 5.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: sqrt(16)
    +  //       returns 1: 4
    +  //       example 2: sqrt(2)
    +  //       returns 2: 1.4142135623730951
    +
    +  return Math.sqrt(x)
    +}
    
  • src/awk/builtin/substr.js+0 23 removed
    @@ -1,23 +0,0 @@
    -module.exports = function substr(str, start, len) {
    -  //      discuss at: https://locutus.io/awk/substr/
    -  // parity verified: GNU AWK 5.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: AWK uses 1-based indexing, so start=1 is the first character
    -  //       example 1: substr('hello', 2, 3)
    -  //       returns 1: 'ell'
    -  //       example 2: substr('hello', 1, 2)
    -  //       returns 2: 'he'
    -  //       example 3: substr('hello', 3)
    -  //       returns 3: 'llo'
    -
    -  if (str === undefined || str === null) {
    -    return ''
    -  }
    -  const s = String(str)
    -  // AWK uses 1-based indexing
    -  const startIdx = Math.max(0, start - 1)
    -  if (len === undefined) {
    -    return s.substring(startIdx)
    -  }
    -  return s.substring(startIdx, startIdx + len)
    -}
    
  • src/awk/builtin/substr.ts+23 0 added
    @@ -0,0 +1,23 @@
    +export function substr(str: string, start: number, len?: number): string {
    +  //      discuss at: https://locutus.io/awk/substr/
    +  // parity verified: GNU AWK 5.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: AWK uses 1-based indexing, so start=1 is the first character
    +  //       example 1: substr('hello', 2, 3)
    +  //       returns 1: 'ell'
    +  //       example 2: substr('hello', 1, 2)
    +  //       returns 2: 'he'
    +  //       example 3: substr('hello', 3)
    +  //       returns 3: 'llo'
    +
    +  if (str === undefined || str === null) {
    +    return ''
    +  }
    +  const s = String(str)
    +  // AWK uses 1-based indexing
    +  const startIdx = Math.max(0, start - 1)
    +  if (len === undefined) {
    +    return s.substring(startIdx)
    +  }
    +  return s.substring(startIdx, startIdx + len)
    +}
    
  • src/awk/builtin/tolower.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function tolower(s) {
    -  //      discuss at: https://locutus.io/awk/tolower/
    -  // parity verified: GNU AWK 5.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: tolower('HELLO')
    -  //       returns 1: 'hello'
    -  //       example 2: tolower('Hello World')
    -  //       returns 2: 'hello world'
    -
    -  return String(s).toLowerCase()
    -}
    
  • src/awk/builtin/tolower.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function tolower(s: string): string {
    +  //      discuss at: https://locutus.io/awk/tolower/
    +  // parity verified: GNU AWK 5.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: tolower('HELLO')
    +  //       returns 1: 'hello'
    +  //       example 2: tolower('Hello World')
    +  //       returns 2: 'hello world'
    +
    +  return String(s).toLowerCase()
    +}
    
  • src/awk/builtin/toupper.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function toupper(s) {
    -  //      discuss at: https://locutus.io/awk/toupper/
    -  // parity verified: GNU AWK 5.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: toupper('hello')
    -  //       returns 1: 'HELLO'
    -  //       example 2: toupper('Hello World')
    -  //       returns 2: 'HELLO WORLD'
    -
    -  return String(s).toUpperCase()
    -}
    
  • src/awk/builtin/toupper.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function toupper(s: string): string {
    +  //      discuss at: https://locutus.io/awk/toupper/
    +  // parity verified: GNU AWK 5.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: toupper('hello')
    +  //       returns 1: 'HELLO'
    +  //       example 2: toupper('Hello World')
    +  //       returns 2: 'HELLO WORLD'
    +
    +  return String(s).toUpperCase()
    +}
    
  • src/c/ctype/index.js+0 8 removed
    @@ -1,8 +0,0 @@
    -module.exports.isalnum = require('./isalnum')
    -module.exports.isalpha = require('./isalpha')
    -module.exports.isdigit = require('./isdigit')
    -module.exports.islower = require('./islower')
    -module.exports.isspace = require('./isspace')
    -module.exports.isupper = require('./isupper')
    -module.exports.tolower = require('./tolower')
    -module.exports.toupper = require('./toupper')
    
  • src/c/ctype/index.ts+8 0 added
    @@ -0,0 +1,8 @@
    +export { isalnum } from './isalnum.ts'
    +export { isalpha } from './isalpha.ts'
    +export { isdigit } from './isdigit.ts'
    +export { islower } from './islower.ts'
    +export { isspace } from './isspace.ts'
    +export { isupper } from './isupper.ts'
    +export { tolower } from './tolower.ts'
    +export { toupper } from './toupper.ts'
    
  • src/c/ctype/isalnum.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function isalnum(c) {
    -  //      discuss at: https://locutus.io/c/ctype/isalnum/
    -  // parity verified: C 23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Checks if the character is alphanumeric (A-Z, a-z, or 0-9).
    -  //       example 1: isalnum('A')
    -  //       returns 1: true
    -  //       example 2: isalnum('5')
    -  //       returns 2: true
    -  //       example 3: isalnum('_')
    -  //       returns 3: false
    -
    -  c = (c + '').charAt(0)
    -  return /^[A-Za-z0-9]$/.test(c)
    -}
    
  • src/c/ctype/isalnum.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function isalnum(c: string): boolean {
    +  //      discuss at: https://locutus.io/c/ctype/isalnum/
    +  // parity verified: C 23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Checks if the character is alphanumeric (A-Z, a-z, or 0-9).
    +  //       example 1: isalnum('A')
    +  //       returns 1: true
    +  //       example 2: isalnum('5')
    +  //       returns 2: true
    +  //       example 3: isalnum('_')
    +  //       returns 3: false
    +
    +  c = (c + '').charAt(0)
    +  return /^[A-Za-z0-9]$/.test(c)
    +}
    
  • src/c/ctype/isalpha.js+0 17 removed
    @@ -1,17 +0,0 @@
    -module.exports = function isalpha(c) {
    -  //      discuss at: https://locutus.io/c/ctype/isalpha/
    -  // parity verified: C 23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Checks if the character is an alphabetic letter (A-Z or a-z).
    -  //       example 1: isalpha('A')
    -  //       returns 1: true
    -  //       example 2: isalpha('z')
    -  //       returns 2: true
    -  //       example 3: isalpha('5')
    -  //       returns 3: false
    -  //       example 4: isalpha(' ')
    -  //       returns 4: false
    -
    -  c = (c + '').charAt(0)
    -  return /^[A-Za-z]$/.test(c)
    -}
    
  • src/c/ctype/isalpha.ts+17 0 added
    @@ -0,0 +1,17 @@
    +export function isalpha(c: string): boolean {
    +  //      discuss at: https://locutus.io/c/ctype/isalpha/
    +  // parity verified: C 23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Checks if the character is an alphabetic letter (A-Z or a-z).
    +  //       example 1: isalpha('A')
    +  //       returns 1: true
    +  //       example 2: isalpha('z')
    +  //       returns 2: true
    +  //       example 3: isalpha('5')
    +  //       returns 3: false
    +  //       example 4: isalpha(' ')
    +  //       returns 4: false
    +
    +  c = (c + '').charAt(0)
    +  return /^[A-Za-z]$/.test(c)
    +}
    
  • src/c/ctype/isdigit.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function isdigit(c) {
    -  //      discuss at: https://locutus.io/c/ctype/isdigit/
    -  // parity verified: C 23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Checks if the character is a decimal digit (0-9).
    -  //       example 1: isdigit('5')
    -  //       returns 1: true
    -  //       example 2: isdigit('0')
    -  //       returns 2: true
    -  //       example 3: isdigit('A')
    -  //       returns 3: false
    -
    -  c = (c + '').charAt(0)
    -  return /^[0-9]$/.test(c)
    -}
    
  • src/c/ctype/isdigit.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function isdigit(c: string): boolean {
    +  //      discuss at: https://locutus.io/c/ctype/isdigit/
    +  // parity verified: C 23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Checks if the character is a decimal digit (0-9).
    +  //       example 1: isdigit('5')
    +  //       returns 1: true
    +  //       example 2: isdigit('0')
    +  //       returns 2: true
    +  //       example 3: isdigit('A')
    +  //       returns 3: false
    +
    +  c = (c + '').charAt(0)
    +  return /^[0-9]$/.test(c)
    +}
    
  • src/c/ctype/islower.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function islower(c) {
    -  //      discuss at: https://locutus.io/c/ctype/islower/
    -  // parity verified: C 23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Checks if the character is a lowercase letter (a-z).
    -  //       example 1: islower('a')
    -  //       returns 1: true
    -  //       example 2: islower('A')
    -  //       returns 2: false
    -  //       example 3: islower('5')
    -  //       returns 3: false
    -
    -  c = (c + '').charAt(0)
    -  return /^[a-z]$/.test(c)
    -}
    
  • src/c/ctype/islower.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function islower(c: string): boolean {
    +  //      discuss at: https://locutus.io/c/ctype/islower/
    +  // parity verified: C 23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Checks if the character is a lowercase letter (a-z).
    +  //       example 1: islower('a')
    +  //       returns 1: true
    +  //       example 2: islower('A')
    +  //       returns 2: false
    +  //       example 3: islower('5')
    +  //       returns 3: false
    +
    +  c = (c + '').charAt(0)
    +  return /^[a-z]$/.test(c)
    +}
    
  • src/c/ctype/isspace.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function isspace(c) {
    -  //  discuss at: https://locutus.io/c/ctype/isspace/
    -  // original by: Kevin van Zonneveld (https://kvz.io)
    -  //      note 1: Checks if the character is a whitespace character.
    -  //      note 1: Includes space, tab, newline, vertical tab, form feed, carriage return.
    -  //   example 1: isspace(' ')
    -  //   returns 1: true
    -  //   example 2: isspace('\t')
    -  //   returns 2: true
    -  //   example 3: isspace('a')
    -  //   returns 3: false
    -
    -  c = (c + '').charAt(0)
    -  return /^[\s]$/.test(c)
    -}
    
  • src/c/ctype/isspace.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function isspace(c: string): boolean {
    +  //  discuss at: https://locutus.io/c/ctype/isspace/
    +  // original by: Kevin van Zonneveld (https://kvz.io)
    +  //      note 1: Checks if the character is a whitespace character.
    +  //      note 1: Includes space, tab, newline, vertical tab, form feed, carriage return.
    +  //   example 1: isspace(' ')
    +  //   returns 1: true
    +  //   example 2: isspace('\t')
    +  //   returns 2: true
    +  //   example 3: isspace('a')
    +  //   returns 3: false
    +
    +  c = (c + '').charAt(0)
    +  return /^[\s]$/.test(c)
    +}
    
  • src/c/ctype/isupper.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function isupper(c) {
    -  //      discuss at: https://locutus.io/c/ctype/isupper/
    -  // parity verified: C 23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Checks if the character is an uppercase letter (A-Z).
    -  //       example 1: isupper('A')
    -  //       returns 1: true
    -  //       example 2: isupper('a')
    -  //       returns 2: false
    -  //       example 3: isupper('5')
    -  //       returns 3: false
    -
    -  c = (c + '').charAt(0)
    -  return /^[A-Z]$/.test(c)
    -}
    
  • src/c/ctype/isupper.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function isupper(c: string): boolean {
    +  //      discuss at: https://locutus.io/c/ctype/isupper/
    +  // parity verified: C 23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Checks if the character is an uppercase letter (A-Z).
    +  //       example 1: isupper('A')
    +  //       returns 1: true
    +  //       example 2: isupper('a')
    +  //       returns 2: false
    +  //       example 3: isupper('5')
    +  //       returns 3: false
    +
    +  c = (c + '').charAt(0)
    +  return /^[A-Z]$/.test(c)
    +}
    
  • src/c/ctype/tolower.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function tolower(c) {
    -  //      discuss at: https://locutus.io/c/ctype/tolower/
    -  // parity verified: C 23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Converts an uppercase letter to lowercase.
    -  //       example 1: tolower('A')
    -  //       returns 1: 'a'
    -  //       example 2: tolower('a')
    -  //       returns 2: 'a'
    -  //       example 3: tolower('5')
    -  //       returns 3: '5'
    -
    -  c = (c + '').charAt(0)
    -  return c.toLowerCase()
    -}
    
  • src/c/ctype/tolower.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function tolower(c: string): string {
    +  //      discuss at: https://locutus.io/c/ctype/tolower/
    +  // parity verified: C 23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Converts an uppercase letter to lowercase.
    +  //       example 1: tolower('A')
    +  //       returns 1: 'a'
    +  //       example 2: tolower('a')
    +  //       returns 2: 'a'
    +  //       example 3: tolower('5')
    +  //       returns 3: '5'
    +
    +  c = (c + '').charAt(0)
    +  return c.toLowerCase()
    +}
    
  • src/c/ctype/toupper.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function toupper(c) {
    -  //      discuss at: https://locutus.io/c/ctype/toupper/
    -  // parity verified: C 23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Converts a lowercase letter to uppercase.
    -  //       example 1: toupper('a')
    -  //       returns 1: 'A'
    -  //       example 2: toupper('A')
    -  //       returns 2: 'A'
    -  //       example 3: toupper('5')
    -  //       returns 3: '5'
    -
    -  c = (c + '').charAt(0)
    -  return c.toUpperCase()
    -}
    
  • src/c/ctype/toupper.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function toupper(c: string): string {
    +  //      discuss at: https://locutus.io/c/ctype/toupper/
    +  // parity verified: C 23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Converts a lowercase letter to uppercase.
    +  //       example 1: toupper('a')
    +  //       returns 1: 'A'
    +  //       example 2: toupper('A')
    +  //       returns 2: 'A'
    +  //       example 3: toupper('5')
    +  //       returns 3: '5'
    +
    +  c = (c + '').charAt(0)
    +  return c.toUpperCase()
    +}
    
  • src/c/index.js+0 2 removed
    @@ -1,2 +0,0 @@
    -module.exports.math = require('./math')
    -module.exports.stdio = require('./stdio')
    
  • src/c/index.ts+5 0 added
    @@ -0,0 +1,5 @@
    +export * as ctype from './ctype/index.ts'
    +export * as math from './math/index.ts'
    +export * as stdio from './stdio/index.ts'
    +export * as stdlib from './stdlib/index.ts'
    +export * as string from './string/index.ts'
    
  • src/clojure/Math/abs.js+0 13 removed
    @@ -1,13 +0,0 @@
    -module.exports = function abs(x) {
    -  //      discuss at: https://locutus.io/clojure/abs/
    -  // parity verified: Clojure 1.12
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: abs(-5)
    -  //       returns 1: 5
    -  //       example 2: abs(3.14)
    -  //       returns 2: 3.14
    -  //       example 3: abs(0)
    -  //       returns 3: 0
    -
    -  return Math.abs(x)
    -}
    
  • src/clojure/Math/abs.ts+13 0 added
    @@ -0,0 +1,13 @@
    +export function abs(x: number): number {
    +  //      discuss at: https://locutus.io/clojure/abs/
    +  // parity verified: Clojure 1.12
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: abs(-5)
    +  //       returns 1: 5
    +  //       example 2: abs(3.14)
    +  //       returns 2: 3.14
    +  //       example 3: abs(0)
    +  //       returns 3: 0
    +
    +  return Math.abs(x)
    +}
    
  • src/clojure/Math/ceil.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function ceil(x) {
    -  //      discuss at: https://locutus.io/clojure/ceil/
    -  // parity verified: Clojure 1.12
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the smallest integer >= x
    -  //       example 1: ceil(4.2)
    -  //       returns 1: 5.0
    -  //       example 2: ceil(-0.5)
    -  //       returns 2: -0.0
    -  //       example 3: ceil(3)
    -  //       returns 3: 3.0
    -
    -  // Clojure's Math/ceil returns a double, so we preserve that behavior
    -  return Math.ceil(x)
    -}
    
  • src/clojure/Math/ceil.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function ceil(x: number): number {
    +  //      discuss at: https://locutus.io/clojure/ceil/
    +  // parity verified: Clojure 1.12
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the smallest integer >= x
    +  //       example 1: ceil(4.2)
    +  //       returns 1: 5.0
    +  //       example 2: ceil(-0.5)
    +  //       returns 2: -0.0
    +  //       example 3: ceil(3)
    +  //       returns 3: 3.0
    +
    +  // Clojure's Math/ceil returns a double, so we preserve that behavior
    +  return Math.ceil(x)
    +}
    
  • src/clojure/Math/floor.js+0 14 removed
    @@ -1,14 +0,0 @@
    -module.exports = function floor(x) {
    -  //      discuss at: https://locutus.io/clojure/floor/
    -  // parity verified: Clojure 1.12
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the largest integer <= x
    -  //       example 1: floor(4.7)
    -  //       returns 1: 4.0
    -  //       example 2: floor(-0.5)
    -  //       returns 2: -1.0
    -  //       example 3: floor(3)
    -  //       returns 3: 3.0
    -
    -  return Math.floor(x)
    -}
    
  • src/clojure/Math/floor.ts+14 0 added
    @@ -0,0 +1,14 @@
    +export function floor(x: number): number {
    +  //      discuss at: https://locutus.io/clojure/floor/
    +  // parity verified: Clojure 1.12
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the largest integer <= x
    +  //       example 1: floor(4.7)
    +  //       returns 1: 4.0
    +  //       example 2: floor(-0.5)
    +  //       returns 2: -1.0
    +  //       example 3: floor(3)
    +  //       returns 3: 3.0
    +
    +  return Math.floor(x)
    +}
    
  • src/clojure/Math/index.js+0 3 removed
    @@ -1,3 +0,0 @@
    -module.exports.abs = require('./abs')
    -module.exports.ceil = require('./ceil')
    -module.exports.floor = require('./floor')
    
  • src/clojure/Math/index.ts+3 0 added
    @@ -0,0 +1,3 @@
    +export { abs } from './abs.ts'
    +export { ceil } from './ceil.ts'
    +export { floor } from './floor.ts'
    
  • src/clojure/string/blank.js+0 17 removed
    @@ -1,17 +0,0 @@
    -module.exports = function blank(s) {
    -  //      discuss at: https://locutus.io/clojure/blank/
    -  // parity verified: Clojure 1.12
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Clojure uses blank?, JS uses blank
    -  //       example 1: blank('')
    -  //       returns 1: true
    -  //       example 2: blank('  ')
    -  //       returns 2: true
    -  //       example 3: blank('hello')
    -  //       returns 3: false
    -
    -  if (s === null || s === undefined) {
    -    return true
    -  }
    -  return String(s).trim().length === 0
    -}
    
  • src/clojure/string/blank.ts+17 0 added
    @@ -0,0 +1,17 @@
    +export function blank(s: string): boolean {
    +  //      discuss at: https://locutus.io/clojure/blank/
    +  // parity verified: Clojure 1.12
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Clojure uses blank?, JS uses blank
    +  //       example 1: blank('')
    +  //       returns 1: true
    +  //       example 2: blank('  ')
    +  //       returns 2: true
    +  //       example 3: blank('hello')
    +  //       returns 3: false
    +
    +  if (s === null || s === undefined) {
    +    return true
    +  }
    +  return String(s).trim().length === 0
    +}
    
  • src/clojure/string/index.js+0 5 removed
    @@ -1,5 +0,0 @@
    -module.exports.blank = require('./blank')
    -module.exports.lower_case = require('./lower_case')
    -module.exports.reverse = require('./reverse')
    -module.exports.trim = require('./trim')
    -module.exports.upper_case = require('./upper_case')
    
  • src/clojure/string/index.ts+5 0 added
    @@ -0,0 +1,5 @@
    +export { blank } from './blank.ts'
    +export { lower_case } from './lower_case.ts'
    +export { reverse } from './reverse.ts'
    +export { trim } from './trim.ts'
    +export { upper_case } from './upper_case.ts'
    
  • src/clojure/string/lower_case.js+0 12 removed
    @@ -1,12 +0,0 @@
    -module.exports = function lower_case(s) {
    -  //      discuss at: https://locutus.io/clojure/lower_case/
    -  // parity verified: Clojure 1.12
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Clojure uses lower-case, JS uses lower_case
    -  //       example 1: lower_case('HELLO')
    -  //       returns 1: 'hello'
    -  //       example 2: lower_case('Hello World')
    -  //       returns 2: 'hello world'
    -
    -  return String(s).toLowerCase()
    -}
    
  • src/clojure/string/lower_case.ts+12 0 added
    @@ -0,0 +1,12 @@
    +export function lower_case(s: string): string {
    +  //      discuss at: https://locutus.io/clojure/lower_case/
    +  // parity verified: Clojure 1.12
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Clojure uses lower-case, JS uses lower_case
    +  //       example 1: lower_case('HELLO')
    +  //       returns 1: 'hello'
    +  //       example 2: lower_case('Hello World')
    +  //       returns 2: 'hello world'
    +
    +  return String(s).toLowerCase()
    +}
    
  • src/clojure/string/reverse.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function reverse(s) {
    -  //      discuss at: https://locutus.io/clojure/reverse/
    -  // parity verified: Clojure 1.12
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: reverse('hello')
    -  //       returns 1: 'olleh'
    -  //       example 2: reverse('abc')
    -  //       returns 2: 'cba'
    -
    -  return String(s).split('').reverse().join('')
    -}
    
  • src/clojure/string/reverse.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function reverse(s: string): string {
    +  //      discuss at: https://locutus.io/clojure/reverse/
    +  // parity verified: Clojure 1.12
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: reverse('hello')
    +  //       returns 1: 'olleh'
    +  //       example 2: reverse('abc')
    +  //       returns 2: 'cba'
    +
    +  return String(s).split('').reverse().join('')
    +}
    
  • src/clojure/string/trim.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function trim(s) {
    -  //      discuss at: https://locutus.io/clojure/trim/
    -  // parity verified: Clojure 1.12
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: trim('  hello  ')
    -  //       returns 1: 'hello'
    -  //       example 2: trim('abc')
    -  //       returns 2: 'abc'
    -
    -  return String(s).trim()
    -}
    
  • src/clojure/string/trim.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function trim(s: string): string {
    +  //      discuss at: https://locutus.io/clojure/trim/
    +  // parity verified: Clojure 1.12
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: trim('  hello  ')
    +  //       returns 1: 'hello'
    +  //       example 2: trim('abc')
    +  //       returns 2: 'abc'
    +
    +  return String(s).trim()
    +}
    
  • src/clojure/string/upper_case.js+0 12 removed
    @@ -1,12 +0,0 @@
    -module.exports = function upper_case(s) {
    -  //      discuss at: https://locutus.io/clojure/upper_case/
    -  // parity verified: Clojure 1.12
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Clojure uses upper-case, JS uses upper_case
    -  //       example 1: upper_case('hello')
    -  //       returns 1: 'HELLO'
    -  //       example 2: upper_case('Hello World')
    -  //       returns 2: 'HELLO WORLD'
    -
    -  return String(s).toUpperCase()
    -}
    
  • src/clojure/string/upper_case.ts+12 0 added
    @@ -0,0 +1,12 @@
    +export function upper_case(s: string): string {
    +  //      discuss at: https://locutus.io/clojure/upper_case/
    +  // parity verified: Clojure 1.12
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Clojure uses upper-case, JS uses upper_case
    +  //       example 1: upper_case('hello')
    +  //       returns 1: 'HELLO'
    +  //       example 2: upper_case('Hello World')
    +  //       returns 2: 'HELLO WORLD'
    +
    +  return String(s).toUpperCase()
    +}
    
  • src/c/math/abs.js+0 16 removed
    @@ -1,16 +0,0 @@
    -module.exports = function abs(mixedNumber) {
    -  //      discuss at: https://locutus.io/c/abs/
    -  // parity verified: C 23
    -  //     original by: Waldo Malqui Silva (https://waldo.malqui.info)
    -  //     improved by: Karol Kowalski
    -  //     improved by: Kevin van Zonneveld (https://kvz.io)
    -  //     improved by: Jonas Raoni Soares Silva (https://www.jsfromhell.com)
    -  //       example 1: abs(-5)
    -  //       returns 1: 5
    -  //       example 2: abs(-42)
    -  //       returns 2: 42
    -  //       example 3: abs(0)
    -  //       returns 3: 0
    -
    -  return Math.abs(mixedNumber) || 0
    -}
    
  • src/c/math/abs.ts+16 0 added
    @@ -0,0 +1,16 @@
    +export function abs(mixedNumber: number): number {
    +  //      discuss at: https://locutus.io/c/abs/
    +  // parity verified: C 23
    +  //     original by: Waldo Malqui Silva (https://waldo.malqui.info)
    +  //     improved by: Karol Kowalski
    +  //     improved by: Kevin van Zonneveld (https://kvz.io)
    +  //     improved by: Jonas Raoni Soares Silva (https://www.jsfromhell.com)
    +  //       example 1: abs(-5)
    +  //       returns 1: 5
    +  //       example 2: abs(-42)
    +  //       returns 2: 42
    +  //       example 3: abs(0)
    +  //       returns 3: 0
    +
    +  return Math.abs(mixedNumber) || 0
    +}
    
  • src/c/math/frexp.js+0 74 removed
    @@ -1,74 +0,0 @@
    -module.exports = function frexp(arg) {
    -  //  discuss at: https://locutus.io/c/frexp/
    -  // original by: Oskar Larsson Högfeldt (https://oskar-lh.name/)
    -  //      note 1: Instead of
    -  //      note 1: double frexp( double arg, int* exp );
    -  //      note 1: this is built as
    -  //      note 1: [double, int] frexp( double arg );
    -  //      note 1: due to the lack of pointers in JavaScript.
    -  //      note 1: See code comments for further information.
    -  //   example 1: frexp(1)
    -  //   returns 1: [0.5, 1]
    -  //   example 2: frexp(1.5)
    -  //   returns 2: [0.75, 1]
    -  //   example 3: frexp(3 * Math.pow(2, 500))
    -  //   returns 3: [0.75, 502]
    -  //   example 4: frexp(-4)
    -  //   returns 4: [-0.5, 3]
    -  //   example 5: frexp(Number.MAX_VALUE)
    -  //   returns 5: [0.9999999999999999, 1024]
    -  //   example 6: frexp(Number.MIN_VALUE)
    -  //   returns 6: [0.5, -1073]
    -  //   example 7: frexp(-Infinity)
    -  //   returns 7: [-Infinity, 0]
    -  //   example 8: frexp(-0)
    -  //   returns 8: [-0, 0]
    -  //   example 9: frexp(NaN)
    -  //   returns 9: [NaN, 0]
    -
    -  // Potential issue with this implementation:
    -  // the precisions of Math.pow and the ** operator are undefined in the ECMAScript standard,
    -  // however, sane implementations should give the same results for Math.pow(2, <integer>) operations
    -
    -  // Like frexp of C and std::frexp of C++,
    -  // but returns an array instead of using a pointer argument for passing the exponent result.
    -  // Object.is(n, frexp(n)[0] * 2 ** frexp(n)[1]) for all number values of n except when Math.isFinite(n) && Math.abs(n) > 2**1023
    -  // Object.is(n, (2 * frexp(n)[0]) * 2 ** (frexp(n)[1] - 1)) for all number values of n
    -  // Object.is(n, frexp(n)[0]) for these values of n: 0, -0, NaN, Infinity, -Infinity
    -  // Math.abs(frexp(n)[0]) is >= 0.5 and < 1.0 for any other number-type value of n
    -  // See https://en.cppreference.com/w/c/numeric/math/frexp for a more detailed description
    -
    -  arg = Number(arg)
    -
    -  const result = [arg, 0]
    -
    -  if (arg !== 0 && Number.isFinite(arg)) {
    -    const absArg = Math.abs(arg)
    -    // Math.log2 was introduced in ES2015, use it when available
    -    const log2 =
    -      Math.log2 ||
    -      function log2(n) {
    -        return Math.log(n) * Math.LOG2E
    -      }
    -    let exp = Math.max(-1023, Math.floor(log2(absArg)) + 1)
    -    let x = absArg * Math.pow(2, -exp)
    -
    -    // These while loops compensate for rounding errors that sometimes occur because of ECMAScript's Math.log2's undefined precision
    -    // and also works around the issue of Math.pow(2, -exp) === Infinity when exp <= -1024
    -    while (x < 0.5) {
    -      x *= 2
    -      exp--
    -    }
    -    while (x >= 1) {
    -      x *= 0.5
    -      exp++
    -    }
    -
    -    if (arg < 0) {
    -      x = -x
    -    }
    -    result[0] = x
    -    result[1] = exp
    -  }
    -  return result
    -}
    
  • src/c/math/frexp.ts+74 0 added
    @@ -0,0 +1,74 @@
    +export function frexp(arg: number): [number, number] {
    +  //  discuss at: https://locutus.io/c/frexp/
    +  // original by: Oskar Larsson Högfeldt (https://oskar-lh.name/)
    +  //      note 1: Instead of
    +  //      note 1: double frexp( double arg, int* exp );
    +  //      note 1: this is built as
    +  //      note 1: [double, int] frexp( double arg );
    +  //      note 1: due to the lack of pointers in JavaScript.
    +  //      note 1: See code comments for further information.
    +  //   example 1: frexp(1)
    +  //   returns 1: [0.5, 1]
    +  //   example 2: frexp(1.5)
    +  //   returns 2: [0.75, 1]
    +  //   example 3: frexp(3 * Math.pow(2, 500))
    +  //   returns 3: [0.75, 502]
    +  //   example 4: frexp(-4)
    +  //   returns 4: [-0.5, 3]
    +  //   example 5: frexp(Number.MAX_VALUE)
    +  //   returns 5: [0.9999999999999999, 1024]
    +  //   example 6: frexp(Number.MIN_VALUE)
    +  //   returns 6: [0.5, -1073]
    +  //   example 7: frexp(-Infinity)
    +  //   returns 7: [-Infinity, 0]
    +  //   example 8: frexp(-0)
    +  //   returns 8: [-0, 0]
    +  //   example 9: frexp(NaN)
    +  //   returns 9: [NaN, 0]
    +
    +  // Potential issue with this implementation:
    +  // the precisions of Math.pow and the ** operator are undefined in the ECMAScript standard,
    +  // however, sane implementations should give the same results for Math.pow(2, <integer>) operations
    +
    +  // Like frexp of C and std::frexp of C++,
    +  // but returns an array instead of using a pointer argument for passing the exponent result.
    +  // Object.is(n, frexp(n)[0] * 2 ** frexp(n)[1]) for all number values of n except when Math.isFinite(n) && Math.abs(n) > 2**1023
    +  // Object.is(n, (2 * frexp(n)[0]) * 2 ** (frexp(n)[1] - 1)) for all number values of n
    +  // Object.is(n, frexp(n)[0]) for these values of n: 0, -0, NaN, Infinity, -Infinity
    +  // Math.abs(frexp(n)[0]) is >= 0.5 and < 1.0 for any other number-type value of n
    +  // See https://en.cppreference.com/w/c/numeric/math/frexp for a more detailed description
    +
    +  arg = Number(arg)
    +
    +  const result: [number, number] = [arg, 0]
    +
    +  if (arg !== 0 && Number.isFinite(arg)) {
    +    const absArg = Math.abs(arg)
    +    // Math.log2 was introduced in ES2015, use it when available
    +    const log2 =
    +      Math.log2 ||
    +      function log2(n) {
    +        return Math.log(n) * Math.LOG2E
    +      }
    +    let exp = Math.max(-1023, Math.floor(log2(absArg)) + 1)
    +    let x = absArg * Math.pow(2, -exp)
    +
    +    // These while loops compensate for rounding errors that sometimes occur because of ECMAScript's Math.log2's undefined precision
    +    // and also works around the issue of Math.pow(2, -exp) === Infinity when exp <= -1024
    +    while (x < 0.5) {
    +      x *= 2
    +      exp--
    +    }
    +    while (x >= 1) {
    +      x *= 0.5
    +      exp++
    +    }
    +
    +    if (arg < 0) {
    +      x = -x
    +    }
    +    result[0] = x
    +    result[1] = exp
    +  }
    +  return result
    +}
    
  • src/c/math/index.js+0 2 removed
    @@ -1,2 +0,0 @@
    -module.exports.abs = require('./abs')
    -module.exports.frexp = require('./frexp')
    
  • src/c/math/index.ts+2 0 added
    @@ -0,0 +1,2 @@
    +export { abs } from './abs.ts'
    +export { frexp } from './frexp.ts'
    
  • src/c/stdio/index.js+0 1 removed
    @@ -1 +0,0 @@
    -module.exports.sprintf = require('./sprintf')
    
  • src/c/stdio/index.ts+1 0 added
    @@ -0,0 +1 @@
    +export { sprintf } from './sprintf.ts'
    
  • src/c/stdio/sprintf.js+0 132 removed
    @@ -1,132 +0,0 @@
    -function pad(str, minLength, padChar, leftJustify) {
    -  const diff = minLength - str.length
    -  const padStr = padChar.repeat(Math.max(0, diff))
    -
    -  return leftJustify ? str + padStr : padStr + str
    -}
    -
    -module.exports = function sprintf(format, ...args) {
    -  // original by: Rafał Kukawski
    -  // bugfixed by: Param Siddharth
    -  //   example 1: sprintf('%+10.*d', 5, 1)
    -  //   returns 1: '    +00001'
    -  //   example 2: sprintf('%s is a %d%% %s %s.', 'Param', 90, 'good', 'boy')
    -  //   returns 2: 'Param is a 90% good boy.'
    -  const placeholderRegex = /%(?:(\d+)\$)?([-+#0 ]*)(\*|\d+)?(?:\.(\*|\d*))?([\s\S])/g
    -
    -  let index = 0
    -
    -  return format.replace(placeholderRegex, function (match, param, flags, width, prec, modifier) {
    -    const leftJustify = flags.includes('-')
    -
    -    // flag '0' is ignored when flag '-' is present
    -    const padChar = leftJustify ? ' ' : flags.split('').reduce((pc, c) => ([' ', '0'].includes(c) ? c : pc), ' ')
    -
    -    const positiveSign = flags.includes('+') ? '+' : flags.includes(' ') ? ' ' : ''
    -
    -    const minWidth = width === '*' ? args[index++] : +width || 0
    -    let precision = prec === '*' ? args[index++] : +prec
    -
    -    if (param && !+param) {
    -      throw new Error('Param index must be greater than 0')
    -    }
    -
    -    if (param && +param > args.length) {
    -      throw new Error('Too few arguments')
    -    }
    -
    -    // compiling with default clang params, mixed positional and non-positional params
    -    // give only a warning
    -    const arg = param ? args[param - 1] : args[index]
    -
    -    if (modifier !== '%') {
    -      index++
    -    }
    -
    -    if (precision === undefined || isNaN(precision)) {
    -      precision = 'eEfFgG'.includes(modifier) ? 6 : modifier === 's' ? String(arg).length : undefined
    -    }
    -
    -    switch (modifier) {
    -      case '%':
    -        return '%'
    -      case 'd':
    -      case 'i': {
    -        const number = Math.trunc(+arg || 0)
    -        const abs = Math.abs(number)
    -        const prefix = number < 0 ? '-' : positiveSign
    -
    -        const str = pad(abs.toString(), precision || 0, '0', false)
    -
    -        if (padChar === '0') {
    -          return prefix + pad(str, minWidth - prefix.length, padChar, leftJustify)
    -        }
    -
    -        return pad(prefix + str, minWidth, padChar, leftJustify)
    -      }
    -      case 'e':
    -      case 'E':
    -      case 'f':
    -      case 'F':
    -      case 'g':
    -      case 'G': {
    -        const number = +arg
    -        const abs = Math.abs(number)
    -        const prefix = number < 0 ? '-' : positiveSign
    -
    -        const op = [Number.prototype.toExponential, Number.prototype.toFixed, Number.prototype.toPrecision][
    -          'efg'.indexOf(modifier.toLowerCase())
    -        ]
    -
    -        const tr = [String.prototype.toLowerCase, String.prototype.toUpperCase]['eEfFgG'.indexOf(modifier) % 2]
    -
    -        const isSpecial = isNaN(abs) || !isFinite(abs)
    -
    -        const str = isSpecial ? abs.toString().substr(0, 3) : op.call(abs, precision)
    -
    -        if (padChar === '0' && !isSpecial) {
    -          return prefix + pad(tr.call(str), minWidth - prefix.length, padChar, leftJustify)
    -        }
    -
    -        return pad(tr.call(prefix + str), minWidth, isSpecial ? ' ' : padChar, leftJustify)
    -      }
    -      case 'b':
    -      case 'o':
    -      case 'u':
    -      case 'x':
    -      case 'X': {
    -        const number = +arg || 0
    -        const intVal = Math.trunc(number) + (number < 0 ? 0xffffffff + 1 : 0)
    -        const base = [2, 8, 10, 16, 16]['bouxX'.indexOf(modifier)]
    -        const prefix = intVal && flags.includes('#') ? ['', '0', '', '0x', '0X']['bouxXX'.indexOf(modifier)] : ''
    -
    -        if (padChar === '0' && prefix) {
    -          return (
    -            prefix +
    -            pad(pad(intVal.toString(base), precision, '0', false), minWidth - prefix.length, padChar, leftJustify)
    -          )
    -        }
    -
    -        return pad(prefix + pad(intVal.toString(base), precision, '0', false), minWidth, padChar, leftJustify)
    -      }
    -      case 'p':
    -      case 'n': {
    -        throw new Error(`'${modifier}' modifier not supported`)
    -      }
    -      case 's': {
    -        return pad(String(arg).substr(0, precision), minWidth, padChar, leftJustify)
    -      }
    -      case 'c': {
    -        // extension, if arg is string, take first char
    -        const chr = typeof arg === 'string' ? arg.charAt(0) : String.fromCharCode(+arg)
    -        return pad(chr, minWidth, padChar, leftJustify)
    -      }
    -      case 'a':
    -      case 'A':
    -        throw new Error(`'${modifier}' modifier not yet implemented`)
    -      default:
    -        // for unknown modifiers, return the modifier char
    -        return modifier
    -    }
    -  })
    -}
    
  • src/c/stdio/sprintf.ts+157 0 added
    @@ -0,0 +1,157 @@
    +function pad(str: string, minLength: number, padChar: string, leftJustify: boolean) {
    +  const diff = minLength - str.length
    +  const padStr = padChar.repeat(Math.max(0, diff))
    +
    +  return leftJustify ? str + padStr : padStr + str
    +}
    +
    +export function sprintf(format: string, ...args: unknown[]): string {
    +  // original by: Rafał Kukawski
    +  // bugfixed by: Param Siddharth
    +  //   example 1: sprintf('%+10.*d', 5, 1)
    +  //   returns 1: '    +00001'
    +  //   example 2: sprintf('%s is a %d%% %s %s.', 'Param', 90, 'good', 'boy')
    +  //   returns 2: 'Param is a 90% good boy.'
    +  const placeholderRegex = /%(?:(\d+)\$)?([-+#0 ]*)(\*|\d+)?(?:\.(\*|\d*))?([\s\S])/g
    +
    +  let index = 0
    +
    +  return format.replace(
    +    placeholderRegex,
    +    function (
    +      _match: string,
    +      param: string | undefined,
    +      flags: string,
    +      width: string | undefined,
    +      prec: string | undefined,
    +      modifier: string,
    +    ) {
    +      const leftJustify = flags.includes('-')
    +
    +      // flag '0' is ignored when flag '-' is present
    +      const padChar = leftJustify
    +        ? ' '
    +        : flags.split('').reduce((pc: string, c: string) => ([' ', '0'].includes(c) ? c : pc), ' ')
    +
    +      const positiveSign = flags.includes('+') ? '+' : flags.includes(' ') ? ' ' : ''
    +
    +      const minWidth = width === '*' ? Number(args[index++] ?? 0) : +(width ?? 0) || 0
    +      let precision: number | undefined =
    +        prec === '*' ? Number(args[index++] ?? 0) : prec === undefined ? Number.NaN : +prec
    +
    +      const paramIndex = param ? +param : 0
    +      if (param && !paramIndex) {
    +        throw new Error('Param index must be greater than 0')
    +      }
    +
    +      if (param && paramIndex > args.length) {
    +        throw new Error('Too few arguments')
    +      }
    +
    +      // compiling with default clang params, mixed positional and non-positional params
    +      // give only a warning
    +      const arg = param ? args[paramIndex - 1] : args[index]
    +
    +      if (modifier !== '%') {
    +        index++
    +      }
    +
    +      if (precision === undefined || isNaN(precision)) {
    +        precision = 'eEfFgG'.includes(modifier) ? 6 : modifier === 's' ? String(arg).length : undefined
    +      }
    +
    +      switch (modifier) {
    +        case '%':
    +          return '%'
    +        case 'd':
    +        case 'i': {
    +          const number = Math.trunc(Number(arg) || 0)
    +          const abs = Math.abs(number)
    +          const prefix = number < 0 ? '-' : positiveSign
    +
    +          const str = pad(abs.toString(), precision || 0, '0', false)
    +
    +          if (padChar === '0') {
    +            return prefix + pad(str, minWidth - prefix.length, padChar, leftJustify)
    +          }
    +
    +          return pad(prefix + str, minWidth, padChar, leftJustify)
    +        }
    +        case 'e':
    +        case 'E':
    +        case 'f':
    +        case 'F':
    +        case 'g':
    +        case 'G': {
    +          const number = Number(arg)
    +          const abs = Math.abs(number)
    +          const prefix = number < 0 ? '-' : positiveSign
    +          const operationIndex = 'efg'.indexOf(modifier.toLowerCase())
    +          const operation = [
    +            (value: number, fractionDigits?: number) => value.toExponential(fractionDigits),
    +            (value: number, digits?: number) => value.toFixed(digits),
    +            (value: number, precisionDigits?: number) => value.toPrecision(precisionDigits),
    +          ][operationIndex]
    +          const transform =
    +            [(value: string) => value.toLowerCase(), (value: string) => value.toUpperCase()][
    +              'eEfFgG'.indexOf(modifier) % 2
    +            ] ?? ((value: string) => value)
    +
    +          const isSpecial = isNaN(abs) || !isFinite(abs)
    +
    +          const str = isSpecial
    +            ? abs.toString().substr(0, 3)
    +            : (operation || ((value: number) => value.toString()))(abs, precision)
    +
    +          if (padChar === '0' && !isSpecial) {
    +            return prefix + pad(transform(str), minWidth - prefix.length, padChar, leftJustify)
    +          }
    +
    +          return pad(transform(prefix + str), minWidth, isSpecial ? ' ' : padChar, leftJustify)
    +        }
    +        case 'b':
    +        case 'o':
    +        case 'u':
    +        case 'x':
    +        case 'X': {
    +          const number = Number(arg) || 0
    +          const intVal = Math.trunc(number) + (number < 0 ? 0xffffffff + 1 : 0)
    +          const base = [2, 8, 10, 16, 16]['bouxX'.indexOf(modifier)] ?? 10
    +          const prefix = intVal && flags.includes('#') ? ['', '0', '', '0x', '0X']['bouxXX'.indexOf(modifier)] : ''
    +
    +          if (padChar === '0' && prefix) {
    +            return (
    +              prefix +
    +              pad(
    +                pad(intVal.toString(base), precision ?? 0, '0', false),
    +                minWidth - prefix.length,
    +                padChar,
    +                leftJustify,
    +              )
    +            )
    +          }
    +
    +          return pad(prefix + pad(intVal.toString(base), precision ?? 0, '0', false), minWidth, padChar, leftJustify)
    +        }
    +        case 'p':
    +        case 'n': {
    +          throw new Error(`'${modifier}' modifier not supported`)
    +        }
    +        case 's': {
    +          return pad(String(arg).substr(0, precision), minWidth, padChar, leftJustify)
    +        }
    +        case 'c': {
    +          // extension, if arg is string, take first char
    +          const chr = typeof arg === 'string' ? arg.charAt(0) : String.fromCharCode(Number(arg))
    +          return pad(chr, minWidth, padChar, leftJustify)
    +        }
    +        case 'a':
    +        case 'A':
    +          throw new Error(`'${modifier}' modifier not yet implemented`)
    +        default:
    +          // for unknown modifiers, return the modifier char
    +          return modifier
    +      }
    +    },
    +  )
    +}
    
  • src/c/stdlib/atof.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function atof(str) {
    -  //  discuss at: https://locutus.io/c/stdlib/atof/
    -  // original by: Kevin van Zonneveld (https://kvz.io)
    -  //      note 1: Converts a string to a floating-point number.
    -  //   example 1: atof('3.14')
    -  //   returns 1: 3.14
    -  //   example 2: atof('  -2.5e10')
    -  //   returns 2: -25000000000
    -  //   example 3: atof('abc')
    -  //   returns 3: 0
    -
    -  str = (str + '').replace(/^\s+/, '')
    -  const result = parseFloat(str)
    -  return isNaN(result) ? 0 : result
    -}
    
  • src/c/stdlib/atof.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function atof(str: string): number {
    +  //  discuss at: https://locutus.io/c/stdlib/atof/
    +  // original by: Kevin van Zonneveld (https://kvz.io)
    +  //      note 1: Converts a string to a floating-point number.
    +  //   example 1: atof('3.14')
    +  //   returns 1: 3.14
    +  //   example 2: atof('  -2.5e10')
    +  //   returns 2: -25000000000
    +  //   example 3: atof('abc')
    +  //   returns 3: 0
    +
    +  str = (str + '').replace(/^\s+/, '')
    +  const result = parseFloat(str)
    +  return isNaN(result) ? 0 : result
    +}
    
  • src/c/stdlib/atoi.js+0 17 removed
    @@ -1,17 +0,0 @@
    -module.exports = function atoi(str) {
    -  //      discuss at: https://locutus.io/c/stdlib/atoi/
    -  // parity verified: C 23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Converts a string to an integer.
    -  //          note 1: Parses until first non-numeric character.
    -  //       example 1: atoi('42')
    -  //       returns 1: 42
    -  //       example 2: atoi('  -123abc')
    -  //       returns 2: -123
    -  //       example 3: atoi('abc')
    -  //       returns 3: 0
    -
    -  str = (str + '').replace(/^\s+/, '')
    -  const result = parseInt(str, 10)
    -  return isNaN(result) ? 0 : result
    -}
    
  • src/c/stdlib/atoi.ts+17 0 added
    @@ -0,0 +1,17 @@
    +export function atoi(str: string): number {
    +  //      discuss at: https://locutus.io/c/stdlib/atoi/
    +  // parity verified: C 23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Converts a string to an integer.
    +  //          note 1: Parses until first non-numeric character.
    +  //       example 1: atoi('42')
    +  //       returns 1: 42
    +  //       example 2: atoi('  -123abc')
    +  //       returns 2: -123
    +  //       example 3: atoi('abc')
    +  //       returns 3: 0
    +
    +  str = (str + '').replace(/^\s+/, '')
    +  const result = parseInt(str, 10)
    +  return isNaN(result) ? 0 : result
    +}
    
  • src/c/stdlib/index.js+0 2 removed
    @@ -1,2 +0,0 @@
    -module.exports.atof = require('./atof')
    -module.exports.atoi = require('./atoi')
    
  • src/c/stdlib/index.ts+2 0 added
    @@ -0,0 +1,2 @@
    +export { atof } from './atof.ts'
    +export { atoi } from './atoi.ts'
    
  • src/c/string/index.js+0 5 removed
    @@ -1,5 +0,0 @@
    -module.exports.strcat = require('./strcat')
    -module.exports.strchr = require('./strchr')
    -module.exports.strcmp = require('./strcmp')
    -module.exports.strlen = require('./strlen')
    -module.exports.strstr = require('./strstr')
    
  • src/c/string/index.ts+5 0 added
    @@ -0,0 +1,5 @@
    +export { strcat } from './strcat.ts'
    +export { strchr } from './strchr.ts'
    +export { strcmp } from './strcmp.ts'
    +export { strlen } from './strlen.ts'
    +export { strstr } from './strstr.ts'
    
  • src/c/string/strcat.js+0 12 removed
    @@ -1,12 +0,0 @@
    -module.exports = function strcat(dest, src) {
    -  //  discuss at: https://locutus.io/c/string/strcat/
    -  // original by: Kevin van Zonneveld (https://kvz.io)
    -  //      note 1: Appends src string to dest string.
    -  //      note 1: In C this modifies dest, but JS strings are immutable so we return a new string.
    -  //   example 1: strcat('Hello, ', 'World!')
    -  //   returns 1: 'Hello, World!'
    -  //   example 2: strcat('abc', 'def')
    -  //   returns 2: 'abcdef'
    -
    -  return dest + '' + (src + '')
    -}
    
  • src/c/string/strcat.ts+12 0 added
    @@ -0,0 +1,12 @@
    +export function strcat(dest: string, src: string): string {
    +  //  discuss at: https://locutus.io/c/string/strcat/
    +  // original by: Kevin van Zonneveld (https://kvz.io)
    +  //      note 1: Appends src string to dest string.
    +  //      note 1: In C this modifies dest, but JS strings are immutable so we return a new string.
    +  //   example 1: strcat('Hello, ', 'World!')
    +  //   returns 1: 'Hello, World!'
    +  //   example 2: strcat('abc', 'def')
    +  //   returns 2: 'abcdef'
    +
    +  return dest + '' + (src + '')
    +}
    
  • src/c/string/strchr.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function strchr(str, c) {
    -  //  discuss at: https://locutus.io/c/string/strchr/
    -  // original by: Kevin van Zonneveld (https://kvz.io)
    -  //      note 1: Returns a pointer to the first occurrence of c in str.
    -  //      note 1: In JS, returns the substring starting from the first occurrence, or null.
    -  //   example 1: strchr('Hello, World!', 'o')
    -  //   returns 1: 'o, World!'
    -  //   example 2: strchr('Hello', 'x')
    -  //   returns 2: null
    -
    -  str = str + ''
    -  c = (c + '').charAt(0)
    -  const idx = str.indexOf(c)
    -  return idx === -1 ? null : str.slice(idx)
    -}
    
  • src/c/string/strchr.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function strchr(str: string, c: string): string | null {
    +  //  discuss at: https://locutus.io/c/string/strchr/
    +  // original by: Kevin van Zonneveld (https://kvz.io)
    +  //      note 1: Returns a pointer to the first occurrence of c in str.
    +  //      note 1: In JS, returns the substring starting from the first occurrence, or null.
    +  //   example 1: strchr('Hello, World!', 'o')
    +  //   returns 1: 'o, World!'
    +  //   example 2: strchr('Hello', 'x')
    +  //   returns 2: null
    +
    +  str = str + ''
    +  c = (c + '').charAt(0)
    +  const idx = str.indexOf(c)
    +  return idx === -1 ? null : str.slice(idx)
    +}
    
  • src/c/string/strcmp.js+0 21 removed
    @@ -1,21 +0,0 @@
    -module.exports = function strcmp(str1, str2) {
    -  //      discuss at: https://locutus.io/c/string/strcmp/
    -  // parity verified: C 23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Compares two strings lexicographically.
    -  //          note 1: Returns < 0 if str1 < str2, 0 if equal, > 0 if str1 > str2.
    -  //       example 1: strcmp('abc', 'abc')
    -  //       returns 1: 0
    -  //       example 2: strcmp('abc', 'abd')
    -  //       returns 2: -1
    -  //       example 3: strcmp('abd', 'abc')
    -  //       returns 3: 1
    -
    -  str1 = str1 + ''
    -  str2 = str2 + ''
    -
    -  if (str1 === str2) {
    -    return 0
    -  }
    -  return str1 < str2 ? -1 : 1
    -}
    
  • src/c/string/strcmp.ts+21 0 added
    @@ -0,0 +1,21 @@
    +export function strcmp(str1: string, str2: string): number {
    +  //      discuss at: https://locutus.io/c/string/strcmp/
    +  // parity verified: C 23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Compares two strings lexicographically.
    +  //          note 1: Returns < 0 if str1 < str2, 0 if equal, > 0 if str1 > str2.
    +  //       example 1: strcmp('abc', 'abc')
    +  //       returns 1: 0
    +  //       example 2: strcmp('abc', 'abd')
    +  //       returns 2: -1
    +  //       example 3: strcmp('abd', 'abc')
    +  //       returns 3: 1
    +
    +  str1 = str1 + ''
    +  str2 = str2 + ''
    +
    +  if (str1 === str2) {
    +    return 0
    +  }
    +  return str1 < str2 ? -1 : 1
    +}
    
  • src/c/string/strlen.js+0 12 removed
    @@ -1,12 +0,0 @@
    -module.exports = function strlen(str) {
    -  //      discuss at: https://locutus.io/c/string/strlen/
    -  // parity verified: C 23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the length of the string.
    -  //       example 1: strlen('hello')
    -  //       returns 1: 5
    -  //       example 2: strlen('')
    -  //       returns 2: 0
    -
    -  return (str + '').length
    -}
    
  • src/c/string/strlen.ts+12 0 added
    @@ -0,0 +1,12 @@
    +export function strlen(str: string): number {
    +  //      discuss at: https://locutus.io/c/string/strlen/
    +  // parity verified: C 23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the length of the string.
    +  //       example 1: strlen('hello')
    +  //       returns 1: 5
    +  //       example 2: strlen('')
    +  //       returns 2: 0
    +
    +  return (str + '').length
    +}
    
  • src/c/string/strstr.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function strstr(haystack, needle) {
    -  //  discuss at: https://locutus.io/c/string/strstr/
    -  // original by: Kevin van Zonneveld (https://kvz.io)
    -  //      note 1: Finds the first occurrence of needle in haystack.
    -  //      note 1: Returns substring starting from the match, or null if not found.
    -  //   example 1: strstr('Hello, World!', 'World')
    -  //   returns 1: 'World!'
    -  //   example 2: strstr('Hello', 'xyz')
    -  //   returns 2: null
    -
    -  haystack = haystack + ''
    -  needle = needle + ''
    -  const idx = haystack.indexOf(needle)
    -  return idx === -1 ? null : haystack.slice(idx)
    -}
    
  • src/c/string/strstr.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function strstr(haystack: string, needle: string): string | null {
    +  //  discuss at: https://locutus.io/c/string/strstr/
    +  // original by: Kevin van Zonneveld (https://kvz.io)
    +  //      note 1: Finds the first occurrence of needle in haystack.
    +  //      note 1: Returns substring starting from the match, or null if not found.
    +  //   example 1: strstr('Hello, World!', 'World')
    +  //   returns 1: 'World!'
    +  //   example 2: strstr('Hello', 'xyz')
    +  //   returns 2: null
    +
    +  haystack = haystack + ''
    +  needle = needle + ''
    +  const idx = haystack.indexOf(needle)
    +  return idx === -1 ? null : haystack.slice(idx)
    +}
    
  • src/elixir/Float/ceil.js+0 16 removed
    @@ -1,16 +0,0 @@
    -module.exports = function ceil(x) {
    -  //      discuss at: https://locutus.io/elixir/ceil/
    -  // parity verified: Elixir 1.18
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the smallest integer >= x
    -  //       example 1: ceil(4.2)
    -  //       returns 1: 5
    -  //       example 2: ceil(-0.5)
    -  //       returns 2: 0
    -  //       example 3: ceil(3)
    -  //       returns 3: 3
    -
    -  const result = Math.ceil(x)
    -  // Convert -0 to 0 for consistency
    -  return Object.is(result, -0) ? 0 : result
    -}
    
  • src/elixir/Float/ceil.ts+16 0 added
    @@ -0,0 +1,16 @@
    +export function ceil(x: number): number {
    +  //      discuss at: https://locutus.io/elixir/ceil/
    +  // parity verified: Elixir 1.18
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the smallest integer >= x
    +  //       example 1: ceil(4.2)
    +  //       returns 1: 5
    +  //       example 2: ceil(-0.5)
    +  //       returns 2: 0
    +  //       example 3: ceil(3)
    +  //       returns 3: 3
    +
    +  const result = Math.ceil(x)
    +  // Convert -0 to 0 for consistency
    +  return Object.is(result, -0) ? 0 : result
    +}
    
  • src/elixir/Float/floor.js+0 14 removed
    @@ -1,14 +0,0 @@
    -module.exports = function floor(x) {
    -  //      discuss at: https://locutus.io/elixir/floor/
    -  // parity verified: Elixir 1.18
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the largest integer <= x
    -  //       example 1: floor(4.7)
    -  //       returns 1: 4
    -  //       example 2: floor(-0.5)
    -  //       returns 2: -1
    -  //       example 3: floor(3)
    -  //       returns 3: 3
    -
    -  return Math.floor(x)
    -}
    
  • src/elixir/Float/floor.ts+14 0 added
    @@ -0,0 +1,14 @@
    +export function floor(x: number): number {
    +  //      discuss at: https://locutus.io/elixir/floor/
    +  // parity verified: Elixir 1.18
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the largest integer <= x
    +  //       example 1: floor(4.7)
    +  //       returns 1: 4
    +  //       example 2: floor(-0.5)
    +  //       returns 2: -1
    +  //       example 3: floor(3)
    +  //       returns 3: 3
    +
    +  return Math.floor(x)
    +}
    
  • src/elixir/Float/index.js+0 2 removed
    @@ -1,2 +0,0 @@
    -module.exports.ceil = require('./ceil')
    -module.exports.floor = require('./floor')
    
  • src/elixir/Float/index.ts+2 0 added
    @@ -0,0 +1,2 @@
    +export { ceil } from './ceil.ts'
    +export { floor } from './floor.ts'
    
  • src/elixir/Kernel/abs.js+0 13 removed
    @@ -1,13 +0,0 @@
    -module.exports = function abs(x) {
    -  //      discuss at: https://locutus.io/elixir/abs/
    -  // parity verified: Elixir 1.18
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: abs(-5)
    -  //       returns 1: 5
    -  //       example 2: abs(3.14)
    -  //       returns 2: 3.14
    -  //       example 3: abs(0)
    -  //       returns 3: 0
    -
    -  return Math.abs(x)
    -}
    
  • src/elixir/Kernel/abs.ts+13 0 added
    @@ -0,0 +1,13 @@
    +export function abs(x: number): number {
    +  //      discuss at: https://locutus.io/elixir/abs/
    +  // parity verified: Elixir 1.18
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: abs(-5)
    +  //       returns 1: 5
    +  //       example 2: abs(3.14)
    +  //       returns 2: 3.14
    +  //       example 3: abs(0)
    +  //       returns 3: 0
    +
    +  return Math.abs(x)
    +}
    
  • src/elixir/Kernel/index.js+0 1 removed
    @@ -1 +0,0 @@
    -module.exports.abs = require('./abs')
    
  • src/elixir/Kernel/index.ts+1 0 added
    @@ -0,0 +1 @@
    +export { abs } from './abs.ts'
    
  • src/elixir/String/downcase.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function downcase(string) {
    -  //      discuss at: https://locutus.io/elixir/downcase/
    -  // parity verified: Elixir 1.18
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: downcase('HELLO')
    -  //       returns 1: 'hello'
    -  //       example 2: downcase('Hello World')
    -  //       returns 2: 'hello world'
    -
    -  return String(string).toLowerCase()
    -}
    
  • src/elixir/String/downcase.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function downcase(string: string): string {
    +  //      discuss at: https://locutus.io/elixir/downcase/
    +  // parity verified: Elixir 1.18
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: downcase('HELLO')
    +  //       returns 1: 'hello'
    +  //       example 2: downcase('Hello World')
    +  //       returns 2: 'hello world'
    +
    +  return String(string).toLowerCase()
    +}
    
  • src/elixir/String/index.js+0 4 removed
    @@ -1,4 +0,0 @@
    -module.exports.downcase = require('./downcase')
    -module.exports.length = require('./length')
    -module.exports.reverse = require('./reverse')
    -module.exports.upcase = require('./upcase')
    
  • src/elixir/String/index.ts+4 0 added
    @@ -0,0 +1,4 @@
    +export { downcase } from './downcase.ts'
    +export { length } from './length.ts'
    +export { reverse } from './reverse.ts'
    +export { upcase } from './upcase.ts'
    
  • src/elixir/String/length.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function length(string) {
    -  //      discuss at: https://locutus.io/elixir/length/
    -  // parity verified: Elixir 1.18
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: length('hello')
    -  //       returns 1: 5
    -  //       example 2: length('')
    -  //       returns 2: 0
    -
    -  return String(string).length
    -}
    
  • src/elixir/String/length.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function length(string: string): number {
    +  //      discuss at: https://locutus.io/elixir/length/
    +  // parity verified: Elixir 1.18
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: length('hello')
    +  //       returns 1: 5
    +  //       example 2: length('')
    +  //       returns 2: 0
    +
    +  return String(string).length
    +}
    
  • src/elixir/String/reverse.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function reverse(string) {
    -  //      discuss at: https://locutus.io/elixir/reverse/
    -  // parity verified: Elixir 1.18
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: reverse('hello')
    -  //       returns 1: 'olleh'
    -  //       example 2: reverse('abc')
    -  //       returns 2: 'cba'
    -
    -  return String(string).split('').reverse().join('')
    -}
    
  • src/elixir/String/reverse.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function reverse(string: string): string {
    +  //      discuss at: https://locutus.io/elixir/reverse/
    +  // parity verified: Elixir 1.18
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: reverse('hello')
    +  //       returns 1: 'olleh'
    +  //       example 2: reverse('abc')
    +  //       returns 2: 'cba'
    +
    +  return String(string).split('').reverse().join('')
    +}
    
  • src/elixir/String/upcase.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function upcase(string) {
    -  //      discuss at: https://locutus.io/elixir/upcase/
    -  // parity verified: Elixir 1.18
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: upcase('hello')
    -  //       returns 1: 'HELLO'
    -  //       example 2: upcase('Hello World')
    -  //       returns 2: 'HELLO WORLD'
    -
    -  return String(string).toUpperCase()
    -}
    
  • src/elixir/String/upcase.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function upcase(string: string): string {
    +  //      discuss at: https://locutus.io/elixir/upcase/
    +  // parity verified: Elixir 1.18
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: upcase('hello')
    +  //       returns 1: 'HELLO'
    +  //       example 2: upcase('Hello World')
    +  //       returns 2: 'HELLO WORLD'
    +
    +  return String(string).toUpperCase()
    +}
    
  • src/golang/index.js+0 1 removed
    @@ -1 +0,0 @@
    -module.exports.strings = require('./strings')
    
  • src/golang/index.ts+2 0 added
    @@ -0,0 +1,2 @@
    +export * as strconv from './strconv/index.ts'
    +export * as strings from './strings/index.ts'
    
  • src/golang/strconv/Atoi.js+0 26 removed
    @@ -1,26 +0,0 @@
    -module.exports = function Atoi(s) {
    -  //      discuss at: https://locutus.io/golang/strconv/Atoi
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Parses a decimal string and returns the integer value.
    -  //          note 1: Returns [value, null] on success, [0, error] on failure.
    -  //       example 1: Atoi('42')[0]
    -  //       returns 1: 42
    -  //       example 2: Atoi('-123')[0]
    -  //       returns 2: -123
    -  //       example 3: Atoi('abc')[0]
    -  //       returns 3: 0
    -
    -  s = (s + '').trim()
    -
    -  if (!/^-?\d+$/.test(s)) {
    -    return [0, new Error('strconv.Atoi: parsing "' + s + '": invalid syntax')]
    -  }
    -
    -  const result = parseInt(s, 10)
    -  if (isNaN(result)) {
    -    return [0, new Error('strconv.Atoi: parsing "' + s + '": invalid syntax')]
    -  }
    -
    -  return [result, null]
    -}
    
  • src/golang/strconv/Atoi.ts+26 0 added
    @@ -0,0 +1,26 @@
    +export function Atoi(s: unknown): [number, Error | null] {
    +  //      discuss at: https://locutus.io/golang/strconv/Atoi
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Parses a decimal string and returns the integer value.
    +  //          note 1: Returns [value, null] on success, [0, error] on failure.
    +  //       example 1: Atoi('42')[0]
    +  //       returns 1: 42
    +  //       example 2: Atoi('-123')[0]
    +  //       returns 2: -123
    +  //       example 3: Atoi('abc')[0]
    +  //       returns 3: 0
    +
    +  const normalized = String(s).trim()
    +
    +  if (!/^-?\d+$/.test(normalized)) {
    +    return [0, new Error('strconv.Atoi: parsing "' + normalized + '": invalid syntax')]
    +  }
    +
    +  const result = parseInt(normalized, 10)
    +  if (isNaN(result)) {
    +    return [0, new Error('strconv.Atoi: parsing "' + normalized + '": invalid syntax')]
    +  }
    +
    +  return [result, null]
    +}
    
  • src/golang/strconv/FormatBool.js+0 12 removed
    @@ -1,12 +0,0 @@
    -module.exports = function FormatBool(b) {
    -  //      discuss at: https://locutus.io/golang/strconv/FormatBool
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns "true" or "false" according to the value of b.
    -  //       example 1: FormatBool(true)
    -  //       returns 1: 'true'
    -  //       example 2: FormatBool(false)
    -  //       returns 2: 'false'
    -
    -  return b ? 'true' : 'false'
    -}
    
  • src/golang/strconv/FormatBool.ts+12 0 added
    @@ -0,0 +1,12 @@
    +export function FormatBool(b: boolean): string {
    +  //      discuss at: https://locutus.io/golang/strconv/FormatBool
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns "true" or "false" according to the value of b.
    +  //       example 1: FormatBool(true)
    +  //       returns 1: 'true'
    +  //       example 2: FormatBool(false)
    +  //       returns 2: 'false'
    +
    +  return b ? 'true' : 'false'
    +}
    
  • src/golang/strconv/FormatInt.js+0 21 removed
    @@ -1,21 +0,0 @@
    -module.exports = function FormatInt(i, base) {
    -  //      discuss at: https://locutus.io/golang/strconv/FormatInt
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the string representation of i in the given base (2 to 36).
    -  //       example 1: FormatInt(42, 10)
    -  //       returns 1: '42'
    -  //       example 2: FormatInt(255, 16)
    -  //       returns 2: 'ff'
    -  //       example 3: FormatInt(10, 2)
    -  //       returns 3: '1010'
    -
    -  i = parseInt(i, 10)
    -  base = parseInt(base, 10) || 10
    -
    -  if (base < 2 || base > 36) {
    -    throw new Error('strconv: illegal base ' + base)
    -  }
    -
    -  return i.toString(base)
    -}
    
  • src/golang/strconv/FormatInt.ts+21 0 added
    @@ -0,0 +1,21 @@
    +export function FormatInt(i: number, base: number): string {
    +  //      discuss at: https://locutus.io/golang/strconv/FormatInt
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the string representation of i in the given base (2 to 36).
    +  //       example 1: FormatInt(42, 10)
    +  //       returns 1: '42'
    +  //       example 2: FormatInt(255, 16)
    +  //       returns 2: 'ff'
    +  //       example 3: FormatInt(10, 2)
    +  //       returns 3: '1010'
    +
    +  i = Number.parseInt(String(i), 10)
    +  base = Number.parseInt(String(base), 10) || 10
    +
    +  if (base < 2 || base > 36) {
    +    throw new Error('strconv: illegal base ' + base)
    +  }
    +
    +  return i.toString(base)
    +}
    
  • src/golang/strconv/index.js+0 6 removed
    @@ -1,6 +0,0 @@
    -module.exports.Atoi = require('./Atoi')
    -module.exports.FormatBool = require('./FormatBool')
    -module.exports.FormatInt = require('./FormatInt')
    -module.exports.Itoa = require('./Itoa')
    -module.exports.ParseBool = require('./ParseBool')
    -module.exports.ParseInt = require('./ParseInt')
    
  • src/golang/strconv/index.ts+6 0 added
    @@ -0,0 +1,6 @@
    +export { Atoi } from './Atoi.ts'
    +export { FormatBool } from './FormatBool.ts'
    +export { FormatInt } from './FormatInt.ts'
    +export { Itoa } from './Itoa.ts'
    +export { ParseBool } from './ParseBool.ts'
    +export { ParseInt } from './ParseInt.ts'
    
  • src/golang/strconv/Itoa.js+0 14 removed
    @@ -1,14 +0,0 @@
    -module.exports = function Itoa(i) {
    -  //      discuss at: https://locutus.io/golang/strconv/Itoa
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Converts an integer to its decimal string representation.
    -  //       example 1: Itoa(42)
    -  //       returns 1: '42'
    -  //       example 2: Itoa(-123)
    -  //       returns 2: '-123'
    -  //       example 3: Itoa(0)
    -  //       returns 3: '0'
    -
    -  return String(parseInt(i, 10))
    -}
    
  • src/golang/strconv/Itoa.ts+14 0 added
    @@ -0,0 +1,14 @@
    +export function Itoa(i: number): string {
    +  //      discuss at: https://locutus.io/golang/strconv/Itoa
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Converts an integer to its decimal string representation.
    +  //       example 1: Itoa(42)
    +  //       returns 1: '42'
    +  //       example 2: Itoa(-123)
    +  //       returns 2: '-123'
    +  //       example 3: Itoa(0)
    +  //       returns 3: '0'
    +
    +  return String(Number.parseInt(String(i), 10))
    +}
    
  • src/golang/strconv/ParseBool.js+0 35 removed
    @@ -1,35 +0,0 @@
    -module.exports = function ParseBool(str) {
    -  //      discuss at: https://locutus.io/golang/strconv/ParseBool
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Parses a boolean string value.
    -  //          note 1: Returns [value, null] on success, [false, error] on failure.
    -  //          note 1: Accepts: 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False
    -  //       example 1: ParseBool('true')[0]
    -  //       returns 1: true
    -  //       example 2: ParseBool('FALSE')[0]
    -  //       returns 2: false
    -  //       example 3: ParseBool('1')[0]
    -  //       returns 3: true
    -
    -  str = (str + '').trim()
    -
    -  switch (str) {
    -    case '1':
    -    case 't':
    -    case 'T':
    -    case 'true':
    -    case 'TRUE':
    -    case 'True':
    -      return [true, null]
    -    case '0':
    -    case 'f':
    -    case 'F':
    -    case 'false':
    -    case 'FALSE':
    -    case 'False':
    -      return [false, null]
    -    default:
    -      return [false, new Error('strconv.ParseBool: parsing "' + str + '": invalid syntax')]
    -  }
    -}
    
  • src/golang/strconv/ParseBool.ts+35 0 added
    @@ -0,0 +1,35 @@
    +export function ParseBool(str: string): [boolean, Error | null] {
    +  //      discuss at: https://locutus.io/golang/strconv/ParseBool
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Parses a boolean string value.
    +  //          note 1: Returns [value, null] on success, [false, error] on failure.
    +  //          note 1: Accepts: 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False
    +  //       example 1: ParseBool('true')[0]
    +  //       returns 1: true
    +  //       example 2: ParseBool('FALSE')[0]
    +  //       returns 2: false
    +  //       example 3: ParseBool('1')[0]
    +  //       returns 3: true
    +
    +  str = (str + '').trim()
    +
    +  switch (str) {
    +    case '1':
    +    case 't':
    +    case 'T':
    +    case 'true':
    +    case 'TRUE':
    +    case 'True':
    +      return [true, null]
    +    case '0':
    +    case 'f':
    +    case 'F':
    +    case 'false':
    +    case 'FALSE':
    +    case 'False':
    +      return [false, null]
    +    default:
    +      return [false, new Error('strconv.ParseBool: parsing "' + str + '": invalid syntax')]
    +  }
    +}
    
  • src/golang/strconv/ParseInt.js+0 27 removed
    @@ -1,27 +0,0 @@
    -module.exports = function ParseInt(s, base, bitSize) {
    -  //      discuss at: https://locutus.io/golang/strconv/ParseInt
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Interprets a string s in the given base (0, 2 to 36).
    -  //          note 1: Returns [value, null] on success, [0, error] on failure.
    -  //       example 1: ParseInt('42', 10, 64)[0]
    -  //       returns 1: 42
    -  //       example 2: ParseInt('ff', 16, 64)[0]
    -  //       returns 2: 255
    -  //       example 3: ParseInt('1010', 2, 64)[0]
    -  //       returns 3: 10
    -
    -  s = (s + '').trim()
    -  base = parseInt(base, 10) || 10
    -
    -  if (base < 2 || base > 36) {
    -    return [0, new Error('strconv.ParseInt: invalid base ' + base)]
    -  }
    -
    -  const result = parseInt(s, base)
    -  if (isNaN(result)) {
    -    return [0, new Error('strconv.ParseInt: parsing "' + s + '": invalid syntax')]
    -  }
    -
    -  return [result, null]
    -}
    
  • src/golang/strconv/ParseInt.ts+27 0 added
    @@ -0,0 +1,27 @@
    +export function ParseInt(s: string, base: number, _bitSize: number): [number, Error | null] {
    +  //      discuss at: https://locutus.io/golang/strconv/ParseInt
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Interprets a string s in the given base (0, 2 to 36).
    +  //          note 1: Returns [value, null] on success, [0, error] on failure.
    +  //       example 1: ParseInt('42', 10, 64)[0]
    +  //       returns 1: 42
    +  //       example 2: ParseInt('ff', 16, 64)[0]
    +  //       returns 2: 255
    +  //       example 3: ParseInt('1010', 2, 64)[0]
    +  //       returns 3: 10
    +
    +  s = (s + '').trim()
    +  base = Number.parseInt(String(base), 10) || 10
    +
    +  if (base < 2 || base > 36) {
    +    return [0, new Error('strconv.ParseInt: invalid base ' + base)]
    +  }
    +
    +  const result = parseInt(s, base)
    +  if (isNaN(result)) {
    +    return [0, new Error('strconv.ParseInt: parsing "' + s + '": invalid syntax')]
    +  }
    +
    +  return [result, null]
    +}
    
  • src/golang/strings/Compare.js+0 19 removed
    @@ -1,19 +0,0 @@
    -module.exports = function Compare(a, b) {
    -  //      discuss at: https://locutus.io/golang/strings/Compare/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: Compare('a', 'b')
    -  //       returns 1: -1
    -  //       example 2: Compare('a', 'a')
    -  //       returns 2: 0
    -  //       example 3: Compare('b', 'a')
    -  //       returns 3: 1
    -
    -  if (a < b) {
    -    return -1
    -  }
    -  if (a > b) {
    -    return 1
    -  }
    -  return 0
    -}
    
  • src/golang/strings/Compare.ts+19 0 added
    @@ -0,0 +1,19 @@
    +export function Compare(a: string, b: string): number {
    +  //      discuss at: https://locutus.io/golang/strings/Compare/
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: Compare('a', 'b')
    +  //       returns 1: -1
    +  //       example 2: Compare('a', 'a')
    +  //       returns 2: 0
    +  //       example 3: Compare('b', 'a')
    +  //       returns 3: 1
    +
    +  if (a < b) {
    +    return -1
    +  }
    +  if (a > b) {
    +    return 1
    +  }
    +  return 0
    +}
    
  • src/golang/strings/ContainsAny.js+0 18 removed
    @@ -1,18 +0,0 @@
    -module.exports = function ContainsAny(s, chars) {
    -  //      discuss at: https://locutus.io/golang/strings/ContainsAny/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: ContainsAny('team', 'i')
    -  //       returns 1: false
    -  //       example 2: ContainsAny('fail', 'ui')
    -  //       returns 2: true
    -  //       example 3: ContainsAny('ure', 'ui')
    -  //       returns 3: true
    -
    -  for (const char of chars) {
    -    if (s.includes(char)) {
    -      return true
    -    }
    -  }
    -  return false
    -}
    
  • src/golang/strings/ContainsAny.ts+18 0 added
    @@ -0,0 +1,18 @@
    +export function ContainsAny(s: string, chars: string): boolean | false {
    +  //      discuss at: https://locutus.io/golang/strings/ContainsAny/
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: ContainsAny('team', 'i')
    +  //       returns 1: false
    +  //       example 2: ContainsAny('fail', 'ui')
    +  //       returns 2: true
    +  //       example 3: ContainsAny('ure', 'ui')
    +  //       returns 3: true
    +
    +  for (const char of chars) {
    +    if (s.includes(char)) {
    +      return true
    +    }
    +  }
    +  return false
    +}
    
  • src/golang/strings/Contains.js+0 9 removed
    @@ -1,9 +0,0 @@
    -module.exports = function Contains(s, substr) {
    -  //      discuss at: https://locutus.io/golang/strings/Contains
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: Contains('Kevin', 'K')
    -  //       returns 1: true
    -
    -  return (s + '').indexOf(substr) !== -1
    -}
    
  • src/golang/strings/Contains.ts+9 0 added
    @@ -0,0 +1,9 @@
    +export function Contains(s: string, substr: string): boolean {
    +  //      discuss at: https://locutus.io/golang/strings/Contains
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: Contains('Kevin', 'K')
    +  //       returns 1: true
    +
    +  return (s + '').indexOf(substr) !== -1
    +}
    
  • src/golang/strings/Count.js+0 33 removed
    @@ -1,33 +0,0 @@
    -module.exports = function Count(s, sep) {
    -  //      discuss at: https://locutus.io/golang/strings/Count
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //        input by: GopherJS (https://www.gopherjs.org/)
    -  //       example 1: Count("cheese", "e")
    -  //       returns 1: 3
    -  //       example 2: Count("five", "") // before & after each rune
    -  //       returns 2: 5
    -
    -  let pos
    -  let n = 0
    -
    -  if (sep.length === 0) {
    -    return s.split(sep).length + 1
    -  } else if (sep.length > s.length) {
    -    return 0
    -  } else if (sep.length === s.length) {
    -    if (sep === s) {
    -      return 1
    -    }
    -    return 0
    -  }
    -  while (true) {
    -    pos = (s + '').indexOf(sep)
    -    if (pos === -1) {
    -      break
    -    }
    -    n = (n + 1) >> 0
    -    s = s.substring((pos + sep.length) >> 0)
    -  }
    -  return n
    -}
    
  • src/golang/strings/Count.ts+33 0 added
    @@ -0,0 +1,33 @@
    +export function Count(s: string, sep: string): number {
    +  //      discuss at: https://locutus.io/golang/strings/Count
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //        input by: GopherJS (https://www.gopherjs.org/)
    +  //       example 1: Count("cheese", "e")
    +  //       returns 1: 3
    +  //       example 2: Count("five", "") // before & after each rune
    +  //       returns 2: 5
    +
    +  let pos
    +  let n = 0
    +
    +  if (sep.length === 0) {
    +    return s.split(sep).length + 1
    +  } else if (sep.length > s.length) {
    +    return 0
    +  } else if (sep.length === s.length) {
    +    if (sep === s) {
    +      return 1
    +    }
    +    return 0
    +  }
    +  while (true) {
    +    pos = (s + '').indexOf(sep)
    +    if (pos === -1) {
    +      break
    +    }
    +    n = (n + 1) >> 0
    +    s = s.substring((pos + sep.length) >> 0)
    +  }
    +  return n
    +}
    
  • src/golang/strings/EqualFold.js+0 13 removed
    @@ -1,13 +0,0 @@
    -module.exports = function EqualFold(s, t) {
    -  //      discuss at: https://locutus.io/golang/strings/EqualFold/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: EqualFold('Go', 'go')
    -  //       returns 1: true
    -  //       example 2: EqualFold('Hello', 'HELLO')
    -  //       returns 2: true
    -  //       example 3: EqualFold('Σ', 'σ')
    -  //       returns 3: true
    -
    -  return s.toLowerCase() === t.toLowerCase()
    -}
    
  • src/golang/strings/EqualFold.ts+13 0 added
    @@ -0,0 +1,13 @@
    +export function EqualFold(s: string, t: string): boolean {
    +  //      discuss at: https://locutus.io/golang/strings/EqualFold/
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: EqualFold('Go', 'go')
    +  //       returns 1: true
    +  //       example 2: EqualFold('Hello', 'HELLO')
    +  //       returns 2: true
    +  //       example 3: EqualFold('Σ', 'σ')
    +  //       returns 3: true
    +
    +  return s.toLowerCase() === t.toLowerCase()
    +}
    
  • src/golang/strings/Fields.js+0 12 removed
    @@ -1,12 +0,0 @@
    -module.exports = function Fields(s) {
    -  //      discuss at: https://locutus.io/golang/strings/Fields/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: Fields('  foo bar  baz   ')
    -  //       returns 1: ['foo', 'bar', 'baz']
    -  //       example 2: Fields('')
    -  //       returns 2: []
    -
    -  // Split by whitespace and filter empty strings
    -  return s.split(/\s+/).filter((field) => field !== '')
    -}
    
  • src/golang/strings/Fields.ts+12 0 added
    @@ -0,0 +1,12 @@
    +export function Fields(s: string): string[] {
    +  //      discuss at: https://locutus.io/golang/strings/Fields/
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: Fields('  foo bar  baz   ')
    +  //       returns 1: ['foo', 'bar', 'baz']
    +  //       example 2: Fields('')
    +  //       returns 2: []
    +
    +  // Split by whitespace and filter empty strings
    +  return s.split(/\s+/).filter((field) => field !== '')
    +}
    
  • src/golang/strings/HasPrefix.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function HasPrefix(s, prefix) {
    -  //      discuss at: https://locutus.io/golang/strings/HasPrefix
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: HasPrefix('Gopher', 'Go')
    -  //       returns 1: true
    -  //       example 2: HasPrefix('Gopher', 'C')
    -  //       returns 2: false
    -  //       example 3: HasPrefix('Gopher', '')
    -  //       returns 3: true
    -
    -  s = s + ''
    -  prefix = prefix + ''
    -  return s.slice(0, prefix.length) === prefix
    -}
    
  • src/golang/strings/HasPrefix.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function HasPrefix(s: string, prefix: string): boolean {
    +  //      discuss at: https://locutus.io/golang/strings/HasPrefix
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: HasPrefix('Gopher', 'Go')
    +  //       returns 1: true
    +  //       example 2: HasPrefix('Gopher', 'C')
    +  //       returns 2: false
    +  //       example 3: HasPrefix('Gopher', '')
    +  //       returns 3: true
    +
    +  s = s + ''
    +  prefix = prefix + ''
    +  return s.slice(0, prefix.length) === prefix
    +}
    
  • src/golang/strings/HasSuffix.js+0 20 removed
    @@ -1,20 +0,0 @@
    -module.exports = function HasSuffix(s, suffix) {
    -  //      discuss at: https://locutus.io/golang/strings/HasSuffix
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: HasSuffix('Amigo', 'go')
    -  //       returns 1: true
    -  //       example 2: HasSuffix('Amigo', 'O')
    -  //       returns 2: false
    -  //       example 3: HasSuffix('Amigo', 'Amigo')
    -  //       returns 3: true
    -  //       example 4: HasSuffix('Amigo', '')
    -  //       returns 4: true
    -
    -  s = s + ''
    -  suffix = suffix + ''
    -  if (suffix.length === 0) {
    -    return true
    -  }
    -  return s.slice(-suffix.length) === suffix
    -}
    
  • src/golang/strings/HasSuffix.ts+20 0 added
    @@ -0,0 +1,20 @@
    +export function HasSuffix(s: string, suffix: string): boolean {
    +  //      discuss at: https://locutus.io/golang/strings/HasSuffix
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: HasSuffix('Amigo', 'go')
    +  //       returns 1: true
    +  //       example 2: HasSuffix('Amigo', 'O')
    +  //       returns 2: false
    +  //       example 3: HasSuffix('Amigo', 'Amigo')
    +  //       returns 3: true
    +  //       example 4: HasSuffix('Amigo', '')
    +  //       returns 4: true
    +
    +  s = s + ''
    +  suffix = suffix + ''
    +  if (suffix.length === 0) {
    +    return true
    +  }
    +  return s.slice(-suffix.length) === suffix
    +}
    
  • src/golang/strings/Index2.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function Index(s, sep) {
    -  //      discuss at: https://locutus.io/golang/strings/Index
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: Index('Kevin', 'K')
    -  //       returns 1: 0
    -  //       example 2: Index('Kevin', 'Z')
    -  //       returns 2: -1
    -
    -  return (s + '').indexOf(sep)
    -}
    
  • src/golang/strings/Index2.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function Index(s: string, sep: string): number {
    +  //      discuss at: https://locutus.io/golang/strings/Index
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: Index('Kevin', 'K')
    +  //       returns 1: 0
    +  //       example 2: Index('Kevin', 'Z')
    +  //       returns 2: -1
    +
    +  return (s + '').indexOf(sep)
    +}
    
  • src/golang/strings/IndexAny.js+0 18 removed
    @@ -1,18 +0,0 @@
    -module.exports = function IndexAny(s, chars) {
    -  //      discuss at: https://locutus.io/golang/strings/IndexAny/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: IndexAny('chicken', 'aeiouy')
    -  //       returns 1: 2
    -  //       example 2: IndexAny('crwth', 'aeiouy')
    -  //       returns 2: -1
    -
    -  let minIndex = -1
    -  for (const char of chars) {
    -    const idx = s.indexOf(char)
    -    if (idx !== -1 && (minIndex === -1 || idx < minIndex)) {
    -      minIndex = idx
    -    }
    -  }
    -  return minIndex
    -}
    
  • src/golang/strings/IndexAny.ts+18 0 added
    @@ -0,0 +1,18 @@
    +export function IndexAny(s: string, chars: string): number {
    +  //      discuss at: https://locutus.io/golang/strings/IndexAny/
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: IndexAny('chicken', 'aeiouy')
    +  //       returns 1: 2
    +  //       example 2: IndexAny('crwth', 'aeiouy')
    +  //       returns 2: -1
    +
    +  let minIndex = -1
    +  for (const char of chars) {
    +    const idx = s.indexOf(char)
    +    if (idx !== -1 && (minIndex === -1 || idx < minIndex)) {
    +      minIndex = idx
    +    }
    +  }
    +  return minIndex
    +}
    
  • src/golang/strings/index.js+0 25 removed
    @@ -1,25 +0,0 @@
    -module.exports.Compare = require('./Compare')
    -module.exports.Contains = require('./Contains')
    -module.exports.ContainsAny = require('./ContainsAny')
    -module.exports.Count = require('./Count')
    -module.exports.EqualFold = require('./EqualFold')
    -module.exports.Fields = require('./Fields')
    -module.exports.HasPrefix = require('./HasPrefix')
    -module.exports.HasSuffix = require('./HasSuffix')
    -module.exports.Index = require('./Index2')
    -module.exports.Index = require('./Index2')
    -module.exports.IndexAny = require('./IndexAny')
    -module.exports.Join = require('./Join')
    -module.exports.LastIndex = require('./LastIndex')
    -module.exports.LastIndexAny = require('./LastIndexAny')
    -module.exports.Repeat = require('./Repeat')
    -module.exports.Replace = require('./Replace')
    -module.exports.Split = require('./Split')
    -module.exports.ToLower = require('./ToLower')
    -module.exports.ToUpper = require('./ToUpper')
    -module.exports.Trim = require('./Trim')
    -module.exports.TrimLeft = require('./TrimLeft')
    -module.exports.TrimPrefix = require('./TrimPrefix')
    -module.exports.TrimRight = require('./TrimRight')
    -module.exports.TrimSpace = require('./TrimSpace')
    -module.exports.TrimSuffix = require('./TrimSuffix')
    
  • src/golang/strings/Index.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function Index(s, substr) {
    -  //      discuss at: https://locutus.io/golang/strings/Index/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: Index('chicken', 'ken')
    -  //       returns 1: 4
    -  //       example 2: Index('chicken', 'dmr')
    -  //       returns 2: -1
    -
    -  return s.indexOf(substr)
    -}
    
  • src/golang/strings/index.ts+24 0 added
    @@ -0,0 +1,24 @@
    +export { Compare } from './Compare.ts'
    +export { Contains } from './Contains.ts'
    +export { ContainsAny } from './ContainsAny.ts'
    +export { Count } from './Count.ts'
    +export { EqualFold } from './EqualFold.ts'
    +export { Fields } from './Fields.ts'
    +export { HasPrefix } from './HasPrefix.ts'
    +export { HasSuffix } from './HasSuffix.ts'
    +export { Index } from './Index2.ts'
    +export { IndexAny } from './IndexAny.ts'
    +export { Join } from './Join.ts'
    +export { LastIndex } from './LastIndex.ts'
    +export { LastIndexAny } from './LastIndexAny.ts'
    +export { Repeat } from './Repeat.ts'
    +export { Replace } from './Replace.ts'
    +export { Split } from './Split.ts'
    +export { ToLower } from './ToLower.ts'
    +export { ToUpper } from './ToUpper.ts'
    +export { Trim } from './Trim.ts'
    +export { TrimLeft } from './TrimLeft.ts'
    +export { TrimPrefix } from './TrimPrefix.ts'
    +export { TrimRight } from './TrimRight.ts'
    +export { TrimSpace } from './TrimSpace.ts'
    +export { TrimSuffix } from './TrimSuffix.ts'
    
  • src/golang/strings/Join.js+0 16 removed
    @@ -1,16 +0,0 @@
    -module.exports = function Join(elems, sep) {
    -  //      discuss at: https://locutus.io/golang/strings/Join
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Concatenates elements of an array to create a single string
    -  //       example 1: Join(['foo', 'bar', 'baz'], ', ')
    -  //       returns 1: 'foo, bar, baz'
    -  //       example 2: Join(['a', 'b', 'c'], '')
    -  //       returns 2: 'abc'
    -
    -  if (!Array.isArray(elems)) {
    -    return ''
    -  }
    -
    -  return elems.join(sep + '')
    -}
    
  • src/golang/strings/Join.ts+16 0 added
    @@ -0,0 +1,16 @@
    +export function Join(elems: unknown[] | unknown, sep: string): string {
    +  //      discuss at: https://locutus.io/golang/strings/Join
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Concatenates elements of an array to create a single string
    +  //       example 1: Join(['foo', 'bar', 'baz'], ', ')
    +  //       returns 1: 'foo, bar, baz'
    +  //       example 2: Join(['a', 'b', 'c'], '')
    +  //       returns 2: 'abc'
    +
    +  if (!Array.isArray(elems)) {
    +    return ''
    +  }
    +
    +  return (elems as unknown[]).join(sep + '')
    +}
    
  • src/golang/strings/LastIndexAny.js+0 18 removed
    @@ -1,18 +0,0 @@
    -module.exports = function LastIndexAny(s, chars) {
    -  //      discuss at: https://locutus.io/golang/strings/LastIndexAny/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: LastIndexAny('go gopher', 'go')
    -  //       returns 1: 4
    -  //       example 2: LastIndexAny('go gopher', 'rodent')
    -  //       returns 2: 8
    -
    -  let maxIndex = -1
    -  for (const char of chars) {
    -    const idx = s.lastIndexOf(char)
    -    if (idx > maxIndex) {
    -      maxIndex = idx
    -    }
    -  }
    -  return maxIndex
    -}
    
  • src/golang/strings/LastIndexAny.ts+18 0 added
    @@ -0,0 +1,18 @@
    +export function LastIndexAny(s: string, chars: string): number {
    +  //      discuss at: https://locutus.io/golang/strings/LastIndexAny/
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: LastIndexAny('go gopher', 'go')
    +  //       returns 1: 4
    +  //       example 2: LastIndexAny('go gopher', 'rodent')
    +  //       returns 2: 8
    +
    +  let maxIndex = -1
    +  for (const char of chars) {
    +    const idx = s.lastIndexOf(char)
    +    if (idx > maxIndex) {
    +      maxIndex = idx
    +    }
    +  }
    +  return maxIndex
    +}
    
  • src/golang/strings/LastIndex.js+0 12 removed
    @@ -1,12 +0,0 @@
    -module.exports = function LastIndex(s, sep) {
    -  //      discuss at: https://locutus.io/golang/strings/LastIndex
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //        input by: GopherJS (https://www.gopherjs.org/)
    -  //       example 1: LastIndex('go gopher', 'go')
    -  //       returns 1: 3
    -  //       example 2: LastIndex('go gopher', 'rodent')
    -  //       returns 2: -1
    -
    -  return parseInt(s.lastIndexOf(sep), 10) >> 0
    -}
    
  • src/golang/strings/LastIndex.ts+12 0 added
    @@ -0,0 +1,12 @@
    +export function LastIndex(s: string, sep: string): number {
    +  //      discuss at: https://locutus.io/golang/strings/LastIndex
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //        input by: GopherJS (https://www.gopherjs.org/)
    +  //       example 1: LastIndex('go gopher', 'go')
    +  //       returns 1: 3
    +  //       example 2: LastIndex('go gopher', 'rodent')
    +  //       returns 2: -1
    +
    +  return s.lastIndexOf(sep)
    +}
    
  • src/golang/strings/Repeat.js+0 19 removed
    @@ -1,19 +0,0 @@
    -module.exports = function Repeat(s, count) {
    -  //      discuss at: https://locutus.io/golang/strings/Repeat
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns a new string consisting of count copies of s
    -  //       example 1: Repeat('na', 2)
    -  //       returns 1: 'nana'
    -  //       example 2: Repeat('ba', 3)
    -  //       returns 2: 'bababa'
    -
    -  s = s + ''
    -  count = parseInt(count, 10)
    -
    -  if (count < 0 || !isFinite(count)) {
    -    throw new Error('strings: negative Repeat count')
    -  }
    -
    -  return s.repeat(count)
    -}
    
  • src/golang/strings/Repeat.ts+19 0 added
    @@ -0,0 +1,19 @@
    +export function Repeat(s: string, count: number): string {
    +  //      discuss at: https://locutus.io/golang/strings/Repeat
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns a new string consisting of count copies of s
    +  //       example 1: Repeat('na', 2)
    +  //       returns 1: 'nana'
    +  //       example 2: Repeat('ba', 3)
    +  //       returns 2: 'bababa'
    +
    +  s = s + ''
    +  count = Number.parseInt(String(count), 10)
    +
    +  if (count < 0 || !isFinite(count)) {
    +    throw new Error('strings: negative Repeat count')
    +  }
    +
    +  return s.repeat(count)
    +}
    
  • src/golang/strings/Replace.js+0 44 removed
    @@ -1,44 +0,0 @@
    -module.exports = function Replace(s, old, newStr, n) {
    -  //      discuss at: https://locutus.io/golang/strings/Replace
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: n is the number of replacements. If n < 0, no limit on replacements.
    -  //       example 1: Replace('oink oink oink', 'k', 'ky', 2)
    -  //       returns 1: 'oinky oinky oink'
    -  //       example 2: Replace('oink oink oink', 'oink', 'moo', -1)
    -  //       returns 2: 'moo moo moo'
    -
    -  s = s + ''
    -  old = old + ''
    -  newStr = newStr + ''
    -
    -  if (old === '') {
    -    return s
    -  }
    -
    -  if (n === 0) {
    -    return s
    -  }
    -
    -  if (n < 0) {
    -    // Replace all occurrences
    -    return s.split(old).join(newStr)
    -  }
    -
    -  // Replace n occurrences
    -  let result = s
    -  let count = 0
    -  let pos = 0
    -
    -  while (count < n) {
    -    const idx = result.indexOf(old, pos)
    -    if (idx === -1) {
    -      break
    -    }
    -    result = result.slice(0, idx) + newStr + result.slice(idx + old.length)
    -    pos = idx + newStr.length
    -    count++
    -  }
    -
    -  return result
    -}
    
  • src/golang/strings/Replace.ts+44 0 added
    @@ -0,0 +1,44 @@
    +export function Replace(s: string, old: string, newStr: string, n: number): string {
    +  //      discuss at: https://locutus.io/golang/strings/Replace
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: n is the number of replacements. If n < 0, no limit on replacements.
    +  //       example 1: Replace('oink oink oink', 'k', 'ky', 2)
    +  //       returns 1: 'oinky oinky oink'
    +  //       example 2: Replace('oink oink oink', 'oink', 'moo', -1)
    +  //       returns 2: 'moo moo moo'
    +
    +  s = s + ''
    +  old = old + ''
    +  newStr = newStr + ''
    +
    +  if (old === '') {
    +    return s
    +  }
    +
    +  if (n === 0) {
    +    return s
    +  }
    +
    +  if (n < 0) {
    +    // Replace all occurrences
    +    return s.split(old).join(newStr)
    +  }
    +
    +  // Replace n occurrences
    +  let result = s
    +  let count = 0
    +  let pos = 0
    +
    +  while (count < n) {
    +    const idx = result.indexOf(old, pos)
    +    if (idx === -1) {
    +      break
    +    }
    +    result = result.slice(0, idx) + newStr + result.slice(idx + old.length)
    +    pos = idx + newStr.length
    +    count++
    +  }
    +
    +  return result
    +}
    
  • src/golang/strings/Split.js+0 24 removed
    @@ -1,24 +0,0 @@
    -module.exports = function Split(s, sep) {
    -  //      discuss at: https://locutus.io/golang/strings/Split
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns an array of substrings split by sep
    -  //       example 1: Split('a,b,c', ',')
    -  //       returns 1: ['a', 'b', 'c']
    -  //       example 2: Split('a man a plan a canal panama', 'a ')
    -  //       returns 2: ['', 'man ', 'plan ', 'canal panama']
    -  //       example 3: Split(' xyz ', '')
    -  //       returns 3: [' ', 'x', 'y', 'z', ' ']
    -  //       example 4: Split('', 'Bernardo O\'Higgins')
    -  //       returns 4: ['']
    -
    -  s = s + ''
    -  sep = sep + ''
    -
    -  if (sep === '') {
    -    // Split into individual characters (like Go's behavior)
    -    return s.split('')
    -  }
    -
    -  return s.split(sep)
    -}
    
  • src/golang/strings/Split.ts+24 0 added
    @@ -0,0 +1,24 @@
    +export function Split(s: string, sep: string): string[] {
    +  //      discuss at: https://locutus.io/golang/strings/Split
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns an array of substrings split by sep
    +  //       example 1: Split('a,b,c', ',')
    +  //       returns 1: ['a', 'b', 'c']
    +  //       example 2: Split('a man a plan a canal panama', 'a ')
    +  //       returns 2: ['', 'man ', 'plan ', 'canal panama']
    +  //       example 3: Split(' xyz ', '')
    +  //       returns 3: [' ', 'x', 'y', 'z', ' ']
    +  //       example 4: Split('', 'Bernardo O\'Higgins')
    +  //       returns 4: ['']
    +
    +  s = s + ''
    +  sep = sep + ''
    +
    +  if (sep === '') {
    +    // Split into individual characters (like Go's behavior)
    +    return s.split('')
    +  }
    +
    +  return s.split(sep)
    +}
    
  • src/golang/strings/ToLower.js+0 9 removed
    @@ -1,9 +0,0 @@
    -module.exports = function ToLower(s) {
    -  //      discuss at: https://locutus.io/golang/strings/ToLower
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: ToLower('Gopher')
    -  //       returns 1: 'gopher'
    -
    -  return (s + '').toLowerCase()
    -}
    
  • src/golang/strings/ToLower.ts+9 0 added
    @@ -0,0 +1,9 @@
    +export function ToLower(s: string): string {
    +  //      discuss at: https://locutus.io/golang/strings/ToLower
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: ToLower('Gopher')
    +  //       returns 1: 'gopher'
    +
    +  return (s + '').toLowerCase()
    +}
    
  • src/golang/strings/ToUpper.js+0 9 removed
    @@ -1,9 +0,0 @@
    -module.exports = function ToUpper(s) {
    -  //      discuss at: https://locutus.io/golang/strings/ToUpper
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: ToUpper('Gopher')
    -  //       returns 1: 'GOPHER'
    -
    -  return (s + '').toUpperCase()
    -}
    
  • src/golang/strings/ToUpper.ts+9 0 added
    @@ -0,0 +1,9 @@
    +export function ToUpper(s: string): string {
    +  //      discuss at: https://locutus.io/golang/strings/ToUpper
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: ToUpper('Gopher')
    +  //       returns 1: 'GOPHER'
    +
    +  return (s + '').toUpperCase()
    +}
    
  • src/golang/strings/Trim.js+0 19 removed
    @@ -1,19 +0,0 @@
    -module.exports = function Trim(s, cutset) {
    -  //      discuss at: https://locutus.io/golang/strings/Trim
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Unlike Go's strings.Trim which removes characters in cutset,
    -  //          note 1: this implementation removes exact matches of cutset from both ends
    -  //       example 1: Trim('!!!Hello, Gophers!!!', '!')
    -  //       returns 1: 'Hello, Gophers'
    -  //       example 2: Trim('  Hello  ', ' ')
    -  //       returns 2: 'Hello'
    -
    -  s = s + ''
    -  cutset = cutset + ''
    -
    -  // Build regex to match cutset characters at start and end
    -  const escaped = cutset.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
    -  const regex = new RegExp('^[' + escaped + ']+|[' + escaped + ']+$', 'g')
    -  return s.replace(regex, '')
    -}
    
  • src/golang/strings/TrimLeft.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function TrimLeft(s, cutset) {
    -  //      discuss at: https://locutus.io/golang/strings/TrimLeft/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: TrimLeft('¡¡¡Hello, Gophers!!!', '!¡')
    -  //       returns 1: 'Hello, Gophers!!!'
    -  //       example 2: TrimLeft('  hello  ', ' ')
    -  //       returns 2: 'hello  '
    -
    -  let start = 0
    -  while (start < s.length && cutset.includes(s[start])) {
    -    start++
    -  }
    -  return s.slice(start)
    -}
    
  • src/golang/strings/TrimLeft.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function TrimLeft(s: string, cutset: string): string {
    +  //      discuss at: https://locutus.io/golang/strings/TrimLeft/
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: TrimLeft('¡¡¡Hello, Gophers!!!', '!¡')
    +  //       returns 1: 'Hello, Gophers!!!'
    +  //       example 2: TrimLeft('  hello  ', ' ')
    +  //       returns 2: 'hello  '
    +
    +  let start = 0
    +  while (start < s.length && cutset.includes(s.charAt(start))) {
    +    start++
    +  }
    +  return s.slice(start)
    +}
    
  • src/golang/strings/TrimPrefix.js+0 14 removed
    @@ -1,14 +0,0 @@
    -module.exports = function TrimPrefix(s, prefix) {
    -  //      discuss at: https://locutus.io/golang/strings/TrimPrefix/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: TrimPrefix('Hello, World!', 'Hello, ')
    -  //       returns 1: 'World!'
    -  //       example 2: TrimPrefix('Hello, World!', 'Goodbye, ')
    -  //       returns 2: 'Hello, World!'
    -
    -  if (s.startsWith(prefix)) {
    -    return s.slice(prefix.length)
    -  }
    -  return s
    -}
    
  • src/golang/strings/TrimPrefix.ts+14 0 added
    @@ -0,0 +1,14 @@
    +export function TrimPrefix(s: string, prefix: string): string {
    +  //      discuss at: https://locutus.io/golang/strings/TrimPrefix/
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: TrimPrefix('Hello, World!', 'Hello, ')
    +  //       returns 1: 'World!'
    +  //       example 2: TrimPrefix('Hello, World!', 'Goodbye, ')
    +  //       returns 2: 'Hello, World!'
    +
    +  if (s.startsWith(prefix)) {
    +    return s.slice(prefix.length)
    +  }
    +  return s
    +}
    
  • src/golang/strings/TrimRight.js+0 15 removed
    @@ -1,15 +0,0 @@
    -module.exports = function TrimRight(s, cutset) {
    -  //      discuss at: https://locutus.io/golang/strings/TrimRight/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: TrimRight('¡¡¡Hello, Gophers!!!', '!¡')
    -  //       returns 1: '¡¡¡Hello, Gophers'
    -  //       example 2: TrimRight('  hello  ', ' ')
    -  //       returns 2: '  hello'
    -
    -  let end = s.length
    -  while (end > 0 && cutset.includes(s[end - 1])) {
    -    end--
    -  }
    -  return s.slice(0, end)
    -}
    
  • src/golang/strings/TrimRight.ts+15 0 added
    @@ -0,0 +1,15 @@
    +export function TrimRight(s: string, cutset: string): string {
    +  //      discuss at: https://locutus.io/golang/strings/TrimRight/
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: TrimRight('¡¡¡Hello, Gophers!!!', '!¡')
    +  //       returns 1: '¡¡¡Hello, Gophers'
    +  //       example 2: TrimRight('  hello  ', ' ')
    +  //       returns 2: '  hello'
    +
    +  let end = s.length
    +  while (end > 0 && cutset.includes(s.charAt(end - 1))) {
    +    end--
    +  }
    +  return s.slice(0, end)
    +}
    
  • src/golang/strings/TrimSpace.js+0 10 removed
    @@ -1,10 +0,0 @@
    -module.exports = function TrimSpace(s) {
    -  //      discuss at: https://locutus.io/golang/strings/TrimSpace
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Removes leading and trailing white space (as defined by Unicode)
    -  //       example 1: TrimSpace(' \t\n Hello, Gophers \n\t\r\n')
    -  //       returns 1: 'Hello, Gophers'
    -
    -  return (s + '').trim()
    -}
    
  • src/golang/strings/TrimSpace.ts+10 0 added
    @@ -0,0 +1,10 @@
    +export function TrimSpace(s: string): string {
    +  //      discuss at: https://locutus.io/golang/strings/TrimSpace
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Removes leading and trailing white space (as defined by Unicode)
    +  //       example 1: TrimSpace(' \t\n Hello, Gophers \n\t\r\n')
    +  //       returns 1: 'Hello, Gophers'
    +
    +  return (s + '').trim()
    +}
    
  • src/golang/strings/TrimSuffix.js+0 14 removed
    @@ -1,14 +0,0 @@
    -module.exports = function TrimSuffix(s, suffix) {
    -  //      discuss at: https://locutus.io/golang/strings/TrimSuffix/
    -  // parity verified: Go 1.23
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: TrimSuffix('Hello, World!', ', World!')
    -  //       returns 1: 'Hello'
    -  //       example 2: TrimSuffix('Hello, World!', 'Goodbye')
    -  //       returns 2: 'Hello, World!'
    -
    -  if (s.endsWith(suffix)) {
    -    return s.slice(0, s.length - suffix.length)
    -  }
    -  return s
    -}
    
  • src/golang/strings/TrimSuffix.ts+14 0 added
    @@ -0,0 +1,14 @@
    +export function TrimSuffix(s: string, suffix: string): string {
    +  //      discuss at: https://locutus.io/golang/strings/TrimSuffix/
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: TrimSuffix('Hello, World!', ', World!')
    +  //       returns 1: 'Hello'
    +  //       example 2: TrimSuffix('Hello, World!', 'Goodbye')
    +  //       returns 2: 'Hello, World!'
    +
    +  if (s.endsWith(suffix)) {
    +    return s.slice(0, s.length - suffix.length)
    +  }
    +  return s
    +}
    
  • src/golang/strings/Trim.ts+19 0 added
    @@ -0,0 +1,19 @@
    +export function Trim(s: string, cutset: string): string {
    +  //      discuss at: https://locutus.io/golang/strings/Trim
    +  // parity verified: Go 1.23
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Unlike Go's strings.Trim which removes characters in cutset,
    +  //          note 1: this implementation removes exact matches of cutset from both ends
    +  //       example 1: Trim('!!!Hello, Gophers!!!', '!')
    +  //       returns 1: 'Hello, Gophers'
    +  //       example 2: Trim('  Hello  ', ' ')
    +  //       returns 2: 'Hello'
    +
    +  s = s + ''
    +  cutset = cutset + ''
    +
    +  // Build regex to match cutset characters at start and end
    +  const escaped = cutset.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
    +  const regex = new RegExp('^[' + escaped + ']+|[' + escaped + ']+$', 'g')
    +  return s.replace(regex, '')
    +}
    
  • src/index.js+0 5 removed
    @@ -1,5 +0,0 @@
    -module.exports.c = require('./c')
    -module.exports.golang = require('./golang')
    -module.exports.php = require('./php')
    -module.exports.python = require('./python')
    -module.exports.ruby = require('./ruby')
    
  • src/index.ts+5 0 added
    @@ -0,0 +1,5 @@
    +export * as c from './c/index.ts'
    +export * as golang from './golang/index.ts'
    +export * as php from './php/index.ts'
    +export * as python from './python/index.ts'
    +export * as ruby from './ruby/index.ts'
    
  • src/julia/Base/abs.js+0 13 removed
    @@ -1,13 +0,0 @@
    -module.exports = function abs(x) {
    -  //      discuss at: https://locutus.io/julia/abs/
    -  // parity verified: Julia 1.11
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: abs(-5)
    -  //       returns 1: 5
    -  //       example 2: abs(3.14)
    -  //       returns 2: 3.14
    -  //       example 3: abs(0)
    -  //       returns 3: 0
    -
    -  return Math.abs(x)
    -}
    
  • src/julia/Base/abs.ts+13 0 added
    @@ -0,0 +1,13 @@
    +export function abs(x: number): number {
    +  //      discuss at: https://locutus.io/julia/abs/
    +  // parity verified: Julia 1.11
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: abs(-5)
    +  //       returns 1: 5
    +  //       example 2: abs(3.14)
    +  //       returns 2: 3.14
    +  //       example 3: abs(0)
    +  //       returns 3: 0
    +
    +  return Math.abs(x)
    +}
    
  • src/julia/Base/ceil.js+0 16 removed
    @@ -1,16 +0,0 @@
    -module.exports = function ceil(x) {
    -  //      discuss at: https://locutus.io/julia/ceil/
    -  // parity verified: Julia 1.11
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the smallest integer >= x
    -  //       example 1: ceil(4.2)
    -  //       returns 1: 5
    -  //       example 2: ceil(-0.5)
    -  //       returns 2: 0
    -  //       example 3: ceil(3)
    -  //       returns 3: 3
    -
    -  const result = Math.ceil(x)
    -  // Convert -0 to 0 for consistency
    -  return Object.is(result, -0) ? 0 : result
    -}
    
  • src/julia/Base/ceil.ts+16 0 added
    @@ -0,0 +1,16 @@
    +export function ceil(x: number): number {
    +  //      discuss at: https://locutus.io/julia/ceil/
    +  // parity verified: Julia 1.11
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the smallest integer >= x
    +  //       example 1: ceil(4.2)
    +  //       returns 1: 5
    +  //       example 2: ceil(-0.5)
    +  //       returns 2: 0
    +  //       example 3: ceil(3)
    +  //       returns 3: 3
    +
    +  const result = Math.ceil(x)
    +  // Convert -0 to 0 for consistency
    +  return Object.is(result, -0) ? 0 : result
    +}
    
  • src/julia/Base/floor.js+0 14 removed
    @@ -1,14 +0,0 @@
    -module.exports = function floor(x) {
    -  //      discuss at: https://locutus.io/julia/floor/
    -  // parity verified: Julia 1.11
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the largest integer <= x
    -  //       example 1: floor(4.7)
    -  //       returns 1: 4
    -  //       example 2: floor(-0.5)
    -  //       returns 2: -1
    -  //       example 3: floor(3)
    -  //       returns 3: 3
    -
    -  return Math.floor(x)
    -}
    
  • src/julia/Base/floor.ts+14 0 added
    @@ -0,0 +1,14 @@
    +export function floor(x: number): number {
    +  //      discuss at: https://locutus.io/julia/floor/
    +  // parity verified: Julia 1.11
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the largest integer <= x
    +  //       example 1: floor(4.7)
    +  //       returns 1: 4
    +  //       example 2: floor(-0.5)
    +  //       returns 2: -1
    +  //       example 3: floor(3)
    +  //       returns 3: 3
    +
    +  return Math.floor(x)
    +}
    
  • src/julia/Base/index.js+0 5 removed
    @@ -1,5 +0,0 @@
    -module.exports.abs = require('./abs')
    -module.exports.ceil = require('./ceil')
    -module.exports.floor = require('./floor')
    -module.exports.lowercase = require('./lowercase')
    -module.exports.uppercase = require('./uppercase')
    
  • src/julia/Base/index.ts+5 0 added
    @@ -0,0 +1,5 @@
    +export { abs } from './abs.ts'
    +export { ceil } from './ceil.ts'
    +export { floor } from './floor.ts'
    +export { lowercase } from './lowercase.ts'
    +export { uppercase } from './uppercase.ts'
    
  • src/julia/Base/lowercase.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function lowercase(s) {
    -  //      discuss at: https://locutus.io/julia/lowercase/
    -  // parity verified: Julia 1.11
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: lowercase('HELLO')
    -  //       returns 1: 'hello'
    -  //       example 2: lowercase('Hello World')
    -  //       returns 2: 'hello world'
    -
    -  return String(s).toLowerCase()
    -}
    
  • src/julia/Base/lowercase.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function lowercase(s: string): string {
    +  //      discuss at: https://locutus.io/julia/lowercase/
    +  // parity verified: Julia 1.11
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: lowercase('HELLO')
    +  //       returns 1: 'hello'
    +  //       example 2: lowercase('Hello World')
    +  //       returns 2: 'hello world'
    +
    +  return String(s).toLowerCase()
    +}
    
  • src/julia/Base/uppercase.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function uppercase(s) {
    -  //      discuss at: https://locutus.io/julia/uppercase/
    -  // parity verified: Julia 1.11
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: uppercase('hello')
    -  //       returns 1: 'HELLO'
    -  //       example 2: uppercase('Hello World')
    -  //       returns 2: 'HELLO WORLD'
    -
    -  return String(s).toUpperCase()
    -}
    
  • src/julia/Base/uppercase.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function uppercase(s: string): string {
    +  //      discuss at: https://locutus.io/julia/uppercase/
    +  // parity verified: Julia 1.11
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: uppercase('hello')
    +  //       returns 1: 'HELLO'
    +  //       example 2: uppercase('Hello World')
    +  //       returns 2: 'HELLO WORLD'
    +
    +  return String(s).toUpperCase()
    +}
    
  • src/lua/math/abs.js+0 13 removed
    @@ -1,13 +0,0 @@
    -module.exports = function abs(x) {
    -  //      discuss at: https://locutus.io/lua/abs/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: abs(-5)
    -  //       returns 1: 5
    -  //       example 2: abs(3.14)
    -  //       returns 2: 3.14
    -  //       example 3: abs(0)
    -  //       returns 3: 0
    -
    -  return Math.abs(x)
    -}
    
  • src/lua/math/abs.ts+13 0 added
    @@ -0,0 +1,13 @@
    +export function abs(x: number): number {
    +  //      discuss at: https://locutus.io/lua/abs/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: abs(-5)
    +  //       returns 1: 5
    +  //       example 2: abs(3.14)
    +  //       returns 2: 3.14
    +  //       example 3: abs(0)
    +  //       returns 3: 0
    +
    +  return Math.abs(x)
    +}
    
  • src/lua/math/ceil.js+0 16 removed
    @@ -1,16 +0,0 @@
    -module.exports = function ceil(x) {
    -  //      discuss at: https://locutus.io/lua/ceil/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the smallest integer >= x
    -  //       example 1: ceil(4.2)
    -  //       returns 1: 5
    -  //       example 2: ceil(-0.5)
    -  //       returns 2: 0
    -  //       example 3: ceil(3)
    -  //       returns 3: 3
    -
    -  const result = Math.ceil(x)
    -  // Convert -0 to 0 for consistency
    -  return Object.is(result, -0) ? 0 : result
    -}
    
  • src/lua/math/ceil.ts+16 0 added
    @@ -0,0 +1,16 @@
    +export function ceil(x: number): number {
    +  //      discuss at: https://locutus.io/lua/ceil/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the smallest integer >= x
    +  //       example 1: ceil(4.2)
    +  //       returns 1: 5
    +  //       example 2: ceil(-0.5)
    +  //       returns 2: 0
    +  //       example 3: ceil(3)
    +  //       returns 3: 3
    +
    +  const result = Math.ceil(x)
    +  // Convert -0 to 0 for consistency
    +  return Object.is(result, -0) ? 0 : result
    +}
    
  • src/lua/math/cos.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function cos(x) {
    -  //      discuss at: https://locutus.io/lua/cos/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: cos(0)
    -  //       returns 1: 1
    -  //       example 2: cos(1)
    -  //       returns 2: 0.5403023058681398
    -
    -  return Math.cos(x)
    -}
    
  • src/lua/math/cos.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function cos(x: number): number {
    +  //      discuss at: https://locutus.io/lua/cos/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: cos(0)
    +  //       returns 1: 1
    +  //       example 2: cos(1)
    +  //       returns 2: 0.5403023058681398
    +
    +  return Math.cos(x)
    +}
    
  • src/lua/math/floor.js+0 14 removed
    @@ -1,14 +0,0 @@
    -module.exports = function floor(x) {
    -  //      discuss at: https://locutus.io/lua/floor/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the largest integer <= x
    -  //       example 1: floor(4.7)
    -  //       returns 1: 4
    -  //       example 2: floor(-0.5)
    -  //       returns 2: -1
    -  //       example 3: floor(3)
    -  //       returns 3: 3
    -
    -  return Math.floor(x)
    -}
    
  • src/lua/math/floor.ts+14 0 added
    @@ -0,0 +1,14 @@
    +export function floor(x: number): number {
    +  //      discuss at: https://locutus.io/lua/floor/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the largest integer <= x
    +  //       example 1: floor(4.7)
    +  //       returns 1: 4
    +  //       example 2: floor(-0.5)
    +  //       returns 2: -1
    +  //       example 3: floor(3)
    +  //       returns 3: 3
    +
    +  return Math.floor(x)
    +}
    
  • src/lua/math/index.js+0 8 removed
    @@ -1,8 +0,0 @@
    -module.exports.abs = require('./abs')
    -module.exports.ceil = require('./ceil')
    -module.exports.cos = require('./cos')
    -module.exports.floor = require('./floor')
    -module.exports.max = require('./max')
    -module.exports.min = require('./min')
    -module.exports.sin = require('./sin')
    -module.exports.sqrt = require('./sqrt')
    
  • src/lua/math/index.ts+8 0 added
    @@ -0,0 +1,8 @@
    +export { abs } from './abs.ts'
    +export { ceil } from './ceil.ts'
    +export { cos } from './cos.ts'
    +export { floor } from './floor.ts'
    +export { max } from './max.ts'
    +export { min } from './min.ts'
    +export { sin } from './sin.ts'
    +export { sqrt } from './sqrt.ts'
    
  • src/lua/math/max.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function max(...args) {
    -  //      discuss at: https://locutus.io/lua/max/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: max(1, 5, 3)
    -  //       returns 1: 5
    -  //       example 2: max(-1, -5)
    -  //       returns 2: -1
    -
    -  return Math.max(...args)
    -}
    
  • src/lua/math/max.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function max(...args: number[]): number {
    +  //      discuss at: https://locutus.io/lua/max/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: max(1, 5, 3)
    +  //       returns 1: 5
    +  //       example 2: max(-1, -5)
    +  //       returns 2: -1
    +
    +  return Math.max(...args)
    +}
    
  • src/lua/math/min.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function min(...args) {
    -  //      discuss at: https://locutus.io/lua/min/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: min(1, 5, 3)
    -  //       returns 1: 1
    -  //       example 2: min(-1, -5)
    -  //       returns 2: -5
    -
    -  return Math.min(...args)
    -}
    
  • src/lua/math/min.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function min(...args: number[]): number {
    +  //      discuss at: https://locutus.io/lua/min/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: min(1, 5, 3)
    +  //       returns 1: 1
    +  //       example 2: min(-1, -5)
    +  //       returns 2: -5
    +
    +  return Math.min(...args)
    +}
    
  • src/lua/math/sin.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function sin(x) {
    -  //      discuss at: https://locutus.io/lua/sin/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: sin(0)
    -  //       returns 1: 0
    -  //       example 2: sin(1)
    -  //       returns 2: 0.8414709848078965
    -
    -  return Math.sin(x)
    -}
    
  • src/lua/math/sin.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function sin(x: number): number {
    +  //      discuss at: https://locutus.io/lua/sin/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: sin(0)
    +  //       returns 1: 0
    +  //       example 2: sin(1)
    +  //       returns 2: 0.8414709848078965
    +
    +  return Math.sin(x)
    +}
    
  • src/lua/math/sqrt.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function sqrt(x) {
    -  //      discuss at: https://locutus.io/lua/sqrt/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: sqrt(16)
    -  //       returns 1: 4
    -  //       example 2: sqrt(2)
    -  //       returns 2: 1.4142135623730951
    -
    -  return Math.sqrt(x)
    -}
    
  • src/lua/math/sqrt.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function sqrt(x: number): number {
    +  //      discuss at: https://locutus.io/lua/sqrt/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: sqrt(16)
    +  //       returns 1: 4
    +  //       example 2: sqrt(2)
    +  //       returns 2: 1.4142135623730951
    +
    +  return Math.sqrt(x)
    +}
    
  • src/lua/string/index.js+0 6 removed
    @@ -1,6 +0,0 @@
    -module.exports.len = require('./len')
    -module.exports.lower = require('./lower')
    -module.exports.rep = require('./rep')
    -module.exports.reverse = require('./reverse')
    -module.exports.sub = require('./sub')
    -module.exports.upper = require('./upper')
    
  • src/lua/string/index.ts+6 0 added
    @@ -0,0 +1,6 @@
    +export { len } from './len.ts'
    +export { lower } from './lower.ts'
    +export { rep } from './rep.ts'
    +export { reverse } from './reverse.ts'
    +export { sub } from './sub.ts'
    +export { upper } from './upper.ts'
    
  • src/lua/string/len.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function len(s) {
    -  //      discuss at: https://locutus.io/lua/len/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: len('hello')
    -  //       returns 1: 5
    -  //       example 2: len('')
    -  //       returns 2: 0
    -
    -  return String(s).length
    -}
    
  • src/lua/string/len.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function len(s: string): number {
    +  //      discuss at: https://locutus.io/lua/len/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: len('hello')
    +  //       returns 1: 5
    +  //       example 2: len('')
    +  //       returns 2: 0
    +
    +  return String(s).length
    +}
    
  • src/lua/string/lower.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function lower(s) {
    -  //      discuss at: https://locutus.io/lua/lower/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: lower('HELLO')
    -  //       returns 1: 'hello'
    -  //       example 2: lower('Hello World')
    -  //       returns 2: 'hello world'
    -
    -  return String(s).toLowerCase()
    -}
    
  • src/lua/string/lower.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function lower(s: string): string {
    +  //      discuss at: https://locutus.io/lua/lower/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: lower('HELLO')
    +  //       returns 1: 'hello'
    +  //       example 2: lower('Hello World')
    +  //       returns 2: 'hello world'
    +
    +  return String(s).toLowerCase()
    +}
    
  • src/lua/string/rep.js+0 14 removed
    @@ -1,14 +0,0 @@
    -module.exports = function rep(s, n) {
    -  //      discuss at: https://locutus.io/lua/rep/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: rep('ab', 3)
    -  //       returns 1: 'ababab'
    -  //       example 2: rep('x', 5)
    -  //       returns 2: 'xxxxx'
    -
    -  if (n <= 0) {
    -    return ''
    -  }
    -  return String(s).repeat(n)
    -}
    
  • src/lua/string/rep.ts+14 0 added
    @@ -0,0 +1,14 @@
    +export function rep(s: string, n: number): string {
    +  //      discuss at: https://locutus.io/lua/rep/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: rep('ab', 3)
    +  //       returns 1: 'ababab'
    +  //       example 2: rep('x', 5)
    +  //       returns 2: 'xxxxx'
    +
    +  if (n <= 0) {
    +    return ''
    +  }
    +  return String(s).repeat(n)
    +}
    
  • src/lua/string/reverse.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function reverse(s) {
    -  //      discuss at: https://locutus.io/lua/reverse/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: reverse('hello')
    -  //       returns 1: 'olleh'
    -  //       example 2: reverse('abc')
    -  //       returns 2: 'cba'
    -
    -  return String(s).split('').reverse().join('')
    -}
    
  • src/lua/string/reverse.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function reverse(s: string): string {
    +  //      discuss at: https://locutus.io/lua/reverse/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: reverse('hello')
    +  //       returns 1: 'olleh'
    +  //       example 2: reverse('abc')
    +  //       returns 2: 'cba'
    +
    +  return String(s).split('').reverse().join('')
    +}
    
  • src/lua/string/sub.js+0 33 removed
    @@ -1,33 +0,0 @@
    -module.exports = function sub(s, i, j) {
    -  //      discuss at: https://locutus.io/lua/sub/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Lua uses 1-based indexing, negative indices count from end
    -  //       example 1: sub('Hello', 1, 3)
    -  //       returns 1: 'Hel'
    -  //       example 2: sub('Hello', 2)
    -  //       returns 2: 'ello'
    -  //       example 3: sub('Hello', -2)
    -  //       returns 3: 'lo'
    -
    -  s = String(s)
    -  const len = s.length
    -
    -  // Convert Lua 1-based index to JS 0-based
    -  // Negative indices count from end in Lua
    -  let start = i < 0 ? len + i : i - 1
    -  let end = j === undefined ? len : j < 0 ? len + j + 1 : j
    -
    -  // Clamp to valid range
    -  if (start < 0) {
    -    start = 0
    -  }
    -  if (end > len) {
    -    end = len
    -  }
    -  if (start >= end) {
    -    return ''
    -  }
    -
    -  return s.substring(start, end)
    -}
    
  • src/lua/string/sub.ts+33 0 added
    @@ -0,0 +1,33 @@
    +export function sub(s: string, i: number, j?: number): string {
    +  //      discuss at: https://locutus.io/lua/sub/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Lua uses 1-based indexing, negative indices count from end
    +  //       example 1: sub('Hello', 1, 3)
    +  //       returns 1: 'Hel'
    +  //       example 2: sub('Hello', 2)
    +  //       returns 2: 'ello'
    +  //       example 3: sub('Hello', -2)
    +  //       returns 3: 'lo'
    +
    +  s = String(s)
    +  const len = s.length
    +
    +  // Convert Lua 1-based index to JS 0-based
    +  // Negative indices count from end in Lua
    +  let start = i < 0 ? len + i : i - 1
    +  let end = j === undefined ? len : j < 0 ? len + j + 1 : j
    +
    +  // Clamp to valid range
    +  if (start < 0) {
    +    start = 0
    +  }
    +  if (end > len) {
    +    end = len
    +  }
    +  if (start >= end) {
    +    return ''
    +  }
    +
    +  return s.substring(start, end)
    +}
    
  • src/lua/string/upper.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function upper(s) {
    -  //      discuss at: https://locutus.io/lua/upper/
    -  // parity verified: Lua 5.4
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: upper('hello')
    -  //       returns 1: 'HELLO'
    -  //       example 2: upper('Hello World')
    -  //       returns 2: 'HELLO WORLD'
    -
    -  return String(s).toUpperCase()
    -}
    
  • src/lua/string/upper.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function upper(s: string): string {
    +  //      discuss at: https://locutus.io/lua/upper/
    +  // parity verified: Lua 5.4
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: upper('hello')
    +  //       returns 1: 'HELLO'
    +  //       example 2: upper('Hello World')
    +  //       returns 2: 'HELLO WORLD'
    +
    +  return String(s).toUpperCase()
    +}
    
  • src/perl/core/index.js+0 5 removed
    @@ -1,5 +0,0 @@
    -module.exports.lc = require('./lc')
    -module.exports.length = require('./length')
    -module.exports.reverse = require('./reverse')
    -module.exports.substr = require('./substr')
    -module.exports.uc = require('./uc')
    
  • src/perl/core/index.ts+5 0 added
    @@ -0,0 +1,5 @@
    +export { lc } from './lc.ts'
    +export { length } from './length.ts'
    +export { reverse } from './reverse.ts'
    +export { substr } from './substr.ts'
    +export { uc } from './uc.ts'
    
  • src/perl/core/lc.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function lc(str) {
    -  //      discuss at: https://locutus.io/perl/lc/
    -  // parity verified: Perl 5.40
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: lc('HELLO')
    -  //       returns 1: 'hello'
    -  //       example 2: lc('Hello World')
    -  //       returns 2: 'hello world'
    -
    -  return String(str).toLowerCase()
    -}
    
  • src/perl/core/lc.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function lc(str: string): string {
    +  //      discuss at: https://locutus.io/perl/lc/
    +  // parity verified: Perl 5.40
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: lc('HELLO')
    +  //       returns 1: 'hello'
    +  //       example 2: lc('Hello World')
    +  //       returns 2: 'hello world'
    +
    +  return String(str).toLowerCase()
    +}
    
  • src/perl/core/length.js+0 16 removed
    @@ -1,16 +0,0 @@
    -module.exports = function length(str) {
    -  //      discuss at: https://locutus.io/perl/length/
    -  // parity verified: Perl 5.40
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: length('hello')
    -  //       returns 1: 5
    -  //       example 2: length('')
    -  //       returns 2: 0
    -  //       example 3: length('hello world')
    -  //       returns 3: 11
    -
    -  if (str === undefined || str === null) {
    -    return undefined
    -  }
    -  return String(str).length
    -}
    
  • src/perl/core/length.ts+16 0 added
    @@ -0,0 +1,16 @@
    +export function length(str: string | null | undefined): number | undefined {
    +  //      discuss at: https://locutus.io/perl/length/
    +  // parity verified: Perl 5.40
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: length('hello')
    +  //       returns 1: 5
    +  //       example 2: length('')
    +  //       returns 2: 0
    +  //       example 3: length('hello world')
    +  //       returns 3: 11
    +
    +  if (str === undefined || str === null) {
    +    return undefined
    +  }
    +  return String(str).length
    +}
    
  • src/perl/core/reverse.js+0 12 removed
    @@ -1,12 +0,0 @@
    -module.exports = function reverse(str) {
    -  //      discuss at: https://locutus.io/perl/reverse/
    -  // parity verified: Perl 5.40
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: In scalar context, reverses string characters
    -  //       example 1: reverse('hello')
    -  //       returns 1: 'olleh'
    -  //       example 2: reverse('abc')
    -  //       returns 2: 'cba'
    -
    -  return String(str).split('').reverse().join('')
    -}
    
  • src/perl/core/reverse.ts+12 0 added
    @@ -0,0 +1,12 @@
    +export function reverse(str: string): string {
    +  //      discuss at: https://locutus.io/perl/reverse/
    +  // parity verified: Perl 5.40
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: In scalar context, reverses string characters
    +  //       example 1: reverse('hello')
    +  //       returns 1: 'olleh'
    +  //       example 2: reverse('abc')
    +  //       returns 2: 'cba'
    +
    +  return String(str).split('').reverse().join('')
    +}
    
  • src/perl/core/substr.js+0 44 removed
    @@ -1,44 +0,0 @@
    -module.exports = function substr(str, offset, length) {
    -  //      discuss at: https://locutus.io/perl/substr/
    -  // parity verified: Perl 5.40
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Perl's offset is 0-based, negative counts from end
    -  //       example 1: substr('hello', 0, 3)
    -  //       returns 1: 'hel'
    -  //       example 2: substr('hello', 1)
    -  //       returns 2: 'ello'
    -  //       example 3: substr('hello', -2)
    -  //       returns 3: 'lo'
    -
    -  str = String(str)
    -  const len = str.length
    -
    -  // Handle negative offset (count from end)
    -  let start = offset < 0 ? len + offset : offset
    -
    -  // Clamp start to valid range
    -  if (start < 0) {
    -    start = 0
    -  }
    -  if (start > len) {
    -    return ''
    -  }
    -
    -  // If length is undefined, return rest of string
    -  if (length === undefined) {
    -    return str.substring(start)
    -  }
    -
    -  // Handle negative length (leave that many chars at end)
    -  let end
    -  if (length < 0) {
    -    end = len + length
    -    if (end < start) {
    -      return ''
    -    }
    -  } else {
    -    end = start + length
    -  }
    -
    -  return str.substring(start, end)
    -}
    
  • src/perl/core/substr.ts+44 0 added
    @@ -0,0 +1,44 @@
    +export function substr(str: string, offset: number, length?: number): string {
    +  //      discuss at: https://locutus.io/perl/substr/
    +  // parity verified: Perl 5.40
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Perl's offset is 0-based, negative counts from end
    +  //       example 1: substr('hello', 0, 3)
    +  //       returns 1: 'hel'
    +  //       example 2: substr('hello', 1)
    +  //       returns 2: 'ello'
    +  //       example 3: substr('hello', -2)
    +  //       returns 3: 'lo'
    +
    +  str = String(str)
    +  const len = str.length
    +
    +  // Handle negative offset (count from end)
    +  let start = offset < 0 ? len + offset : offset
    +
    +  // Clamp start to valid range
    +  if (start < 0) {
    +    start = 0
    +  }
    +  if (start > len) {
    +    return ''
    +  }
    +
    +  // If length is undefined, return rest of string
    +  if (length === undefined) {
    +    return str.substring(start)
    +  }
    +
    +  // Handle negative length (leave that many chars at end)
    +  let end
    +  if (length < 0) {
    +    end = len + length
    +    if (end < start) {
    +      return ''
    +    }
    +  } else {
    +    end = start + length
    +  }
    +
    +  return str.substring(start, end)
    +}
    
  • src/perl/core/uc.js+0 11 removed
    @@ -1,11 +0,0 @@
    -module.exports = function uc(str) {
    -  //      discuss at: https://locutus.io/perl/uc/
    -  // parity verified: Perl 5.40
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //       example 1: uc('hello')
    -  //       returns 1: 'HELLO'
    -  //       example 2: uc('Hello World')
    -  //       returns 2: 'HELLO WORLD'
    -
    -  return String(str).toUpperCase()
    -}
    
  • src/perl/core/uc.ts+11 0 added
    @@ -0,0 +1,11 @@
    +export function uc(str: string): string {
    +  //      discuss at: https://locutus.io/perl/uc/
    +  // parity verified: Perl 5.40
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //       example 1: uc('hello')
    +  //       returns 1: 'HELLO'
    +  //       example 2: uc('Hello World')
    +  //       returns 2: 'HELLO WORLD'
    +
    +  return String(str).toUpperCase()
    +}
    
  • src/perl/POSIX/ceil.js+0 16 removed
    @@ -1,16 +0,0 @@
    -module.exports = function ceil(x) {
    -  //      discuss at: https://locutus.io/perl/ceil/
    -  // parity verified: Perl 5.40
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the smallest integer >= x
    -  //       example 1: ceil(4.2)
    -  //       returns 1: 5
    -  //       example 2: ceil(-0.5)
    -  //       returns 2: 0
    -  //       example 3: ceil(3)
    -  //       returns 3: 3
    -
    -  const result = Math.ceil(x)
    -  // Convert -0 to 0 for consistency
    -  return Object.is(result, -0) ? 0 : result
    -}
    
  • src/perl/POSIX/ceil.ts+16 0 added
    @@ -0,0 +1,16 @@
    +export function ceil(x: number): number {
    +  //      discuss at: https://locutus.io/perl/ceil/
    +  // parity verified: Perl 5.40
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the smallest integer >= x
    +  //       example 1: ceil(4.2)
    +  //       returns 1: 5
    +  //       example 2: ceil(-0.5)
    +  //       returns 2: 0
    +  //       example 3: ceil(3)
    +  //       returns 3: 3
    +
    +  const result = Math.ceil(x)
    +  // Convert -0 to 0 for consistency
    +  return Object.is(result, -0) ? 0 : result
    +}
    
  • src/perl/POSIX/floor.js+0 14 removed
    @@ -1,14 +0,0 @@
    -module.exports = function floor(x) {
    -  //      discuss at: https://locutus.io/perl/floor/
    -  // parity verified: Perl 5.40
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //          note 1: Returns the largest integer <= x
    -  //       example 1: floor(4.7)
    -  //       returns 1: 4
    -  //       example 2: floor(-0.5)
    -  //       returns 2: -1
    -  //       example 3: floor(3)
    -  //       returns 3: 3
    -
    -  return Math.floor(x)
    -}
    
  • src/perl/POSIX/floor.ts+14 0 added
    @@ -0,0 +1,14 @@
    +export function floor(x: number): number {
    +  //      discuss at: https://locutus.io/perl/floor/
    +  // parity verified: Perl 5.40
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //          note 1: Returns the largest integer <= x
    +  //       example 1: floor(4.7)
    +  //       returns 1: 4
    +  //       example 2: floor(-0.5)
    +  //       returns 2: -1
    +  //       example 3: floor(3)
    +  //       returns 3: 3
    +
    +  return Math.floor(x)
    +}
    
  • src/perl/POSIX/index.js+0 2 removed
    @@ -1,2 +0,0 @@
    -module.exports.ceil = require('./ceil')
    -module.exports.floor = require('./floor')
    
  • src/perl/POSIX/index.ts+2 0 added
    @@ -0,0 +1,2 @@
    +export { ceil } from './ceil.ts'
    +export { floor } from './floor.ts'
    
  • src/php/array/array_change_key_case.js+0 36 removed
    @@ -1,36 +0,0 @@
    -module.exports = function array_change_key_case(array, cs) {
    -  //  discuss at: https://locutus.io/php/array_change_key_case/
    -  // original by: Ates Goral (https://magnetiq.com)
    -  // improved by: marrtins
    -  // improved by: Brett Zamir (https://brett-zamir.me)
    -  //   example 1: array_change_key_case(42)
    -  //   returns 1: false
    -  //   example 2: array_change_key_case([ 3, 5 ])
    -  //   returns 2: [3, 5]
    -  //   example 3: array_change_key_case({ FuBaR: 42 })
    -  //   returns 3: {"fubar": 42}
    -  //   example 4: array_change_key_case({ FuBaR: 42 }, 'CASE_LOWER')
    -  //   returns 4: {"fubar": 42}
    -  //   example 5: array_change_key_case({ FuBaR: 42 }, 'CASE_UPPER')
    -  //   returns 5: {"FUBAR": 42}
    -  //   example 6: array_change_key_case({ FuBaR: 42 }, 2)
    -  //   returns 6: {"FUBAR": 42}
    -
    -  let caseFnc
    -  let key
    -  const tmpArr = {}
    -
    -  if (Array.isArray(array)) {
    -    return array
    -  }
    -
    -  if (array && typeof array === 'object') {
    -    caseFnc = !cs || cs === 'CASE_LOWER' ? 'toLowerCase' : 'toUpperCase'
    -    for (key in array) {
    -      tmpArr[key[caseFnc]()] = array[key]
    -    }
    -    return tmpArr
    -  }
    -
    -  return false
    -}
    
  • src/php/array/array_change_key_case.ts+53 0 added
    @@ -0,0 +1,53 @@
    +import { type PhpArrayLike, type PhpAssoc, type PhpRuntimeValue, toPhpArrayObject } from '../_helpers/_phpTypes.ts'
    +
    +type ChangeValue = PhpRuntimeValue
    +type ArrayChangeInput<TValue extends ChangeValue> = number | PhpArrayLike<TValue> | null
    +type ChangeKeyCaseMode = 0 | 1 | 2 | 'CASE_LOWER' | 'CASE_UPPER'
    +
    +export function array_change_key_case(array: number | null, cs?: ChangeKeyCaseMode): false
    +export function array_change_key_case<TValue extends ChangeValue>(array: TValue[], cs?: ChangeKeyCaseMode): TValue[]
    +export function array_change_key_case<TValue extends ChangeValue>(
    +  array: PhpAssoc<TValue>,
    +  cs?: ChangeKeyCaseMode,
    +): PhpAssoc<TValue>
    +export function array_change_key_case<TValue extends ChangeValue>(
    +  array: ArrayChangeInput<TValue>,
    +  cs?: ChangeKeyCaseMode,
    +): PhpArrayLike<TValue> | false {
    +  //  discuss at: https://locutus.io/php/array_change_key_case/
    +  // original by: Ates Goral (https://magnetiq.com)
    +  // improved by: marrtins
    +  // improved by: Brett Zamir (https://brett-zamir.me)
    +  //   example 1: array_change_key_case(42)
    +  //   returns 1: false
    +  //   example 2: array_change_key_case([ 3, 5 ])
    +  //   returns 2: [3, 5]
    +  //   example 3: array_change_key_case({ FuBaR: 42 })
    +  //   returns 3: {"fubar": 42}
    +  //   example 4: array_change_key_case({ FuBaR: 42 }, 'CASE_LOWER')
    +  //   returns 4: {"fubar": 42}
    +  //   example 5: array_change_key_case({ FuBaR: 42 }, 'CASE_UPPER')
    +  //   returns 5: {"FUBAR": 42}
    +  //   example 6: array_change_key_case({ FuBaR: 42 }, 2)
    +  //   returns 6: {"FUBAR": 42}
    +
    +  let result: PhpArrayLike<TValue> | false
    +
    +  if (Array.isArray(array)) {
    +    result = array
    +  } else if (!array || typeof array !== 'object') {
    +    result = false
    +  } else {
    +    const caseFunction: 'toLowerCase' | 'toUpperCase' =
    +      cs === undefined || cs === 0 || cs === 'CASE_LOWER' ? 'toLowerCase' : 'toUpperCase'
    +    const source = toPhpArrayObject<TValue>(array)
    +    const transformed: PhpAssoc<TValue> = {}
    +
    +    for (const [key, value] of Object.entries(source)) {
    +      transformed[key[caseFunction]()] = value
    +    }
    +    result = transformed
    +  }
    +
    +  return result
    +}
    
  • src/php/array/array_chunk.js+0 60 removed
    @@ -1,60 +0,0 @@
    -module.exports = function array_chunk(input, size, preserveKeys) {
    -  //  discuss at: https://locutus.io/php/array_chunk/
    -  // original by: Carlos R. L. Rodrigues (https://www.jsfromhell.com)
    -  // improved by: Brett Zamir (https://brett-zamir.me)
    -  //      note 1: Important note: Per the ECMAScript specification,
    -  //      note 1: objects may not always iterate in a predictable order
    -  //   example 1: array_chunk(['Kevin', 'van', 'Zonneveld'], 2)
    -  //   returns 1: [['Kevin', 'van'], ['Zonneveld']]
    -  //   example 2: array_chunk(['Kevin', 'van', 'Zonneveld'], 2, true)
    -  //   returns 2: [{0:'Kevin', 1:'van'}, {2: 'Zonneveld'}]
    -  //   example 3: array_chunk({1:'Kevin', 2:'van', 3:'Zonneveld'}, 2)
    -  //   returns 3: [['Kevin', 'van'], ['Zonneveld']]
    -  //   example 4: array_chunk({1:'Kevin', 2:'van', 3:'Zonneveld'}, 2, true)
    -  //   returns 4: [{1: 'Kevin', 2: 'van'}, {3: 'Zonneveld'}]
    -
    -  let x
    -  let p = ''
    -  let i = 0
    -  let c = -1
    -  const l = input.length || 0
    -  const n = []
    -
    -  if (size < 1) {
    -    return null
    -  }
    -
    -  if (Array.isArray(input)) {
    -    if (preserveKeys) {
    -      while (i < l) {
    -        ;(x = i % size) ? (n[c][i] = input[i]) : (n[++c] = {})
    -        n[c][i] = input[i]
    -        i++
    -      }
    -    } else {
    -      while (i < l) {
    -        ;(x = i % size) ? (n[c][x] = input[i]) : (n[++c] = [input[i]])
    -        i++
    -      }
    -    }
    -  } else {
    -    if (preserveKeys) {
    -      for (p in input) {
    -        if (input.hasOwnProperty(p)) {
    -          ;(x = i % size) ? (n[c][p] = input[p]) : (n[++c] = {})
    -          n[c][p] = input[p]
    -          i++
    -        }
    -      }
    -    } else {
    -      for (p in input) {
    -        if (input.hasOwnProperty(p)) {
    -          ;(x = i % size) ? (n[c][x] = input[p]) : (n[++c] = [input[p]])
    -          i++
    -        }
    -      }
    -    }
    -  }
    -
    -  return n
    -}
    
  • src/php/array/array_chunk.ts+77 0 added
    @@ -0,0 +1,77 @@
    +import type { PhpAssoc } from '../_helpers/_phpTypes.ts'
    +
    +type ArrayChunkInput<T> = T[] | PhpAssoc<T>
    +type ArrayChunkOutput<T> = T[] | PhpAssoc<T>
    +
    +export function array_chunk<T>(
    +  input: ArrayChunkInput<T>,
    +  size: number,
    +  preserveKeys?: boolean,
    +): ArrayChunkOutput<T>[] | null {
    +  //  discuss at: https://locutus.io/php/array_chunk/
    +  // original by: Carlos R. L. Rodrigues (https://www.jsfromhell.com)
    +  // improved by: Brett Zamir (https://brett-zamir.me)
    +  //      note 1: Important note: Per the ECMAScript specification,
    +  //      note 1: objects may not always iterate in a predictable order
    +  //   example 1: array_chunk(['Kevin', 'van', 'Zonneveld'], 2)
    +  //   returns 1: [['Kevin', 'van'], ['Zonneveld']]
    +  //   example 2: array_chunk(['Kevin', 'van', 'Zonneveld'], 2, true)
    +  //   returns 2: [{0:'Kevin', 1:'van'}, {2: 'Zonneveld'}]
    +  //   example 3: array_chunk({1:'Kevin', 2:'van', 3:'Zonneveld'}, 2)
    +  //   returns 3: [['Kevin', 'van'], ['Zonneveld']]
    +  //   example 4: array_chunk({1:'Kevin', 2:'van', 3:'Zonneveld'}, 2, true)
    +  //   returns 4: [{1: 'Kevin', 2: 'van'}, {3: 'Zonneveld'}]
    +
    +  if (size < 1) {
    +    return null
    +  }
    +
    +  const keepKeys = Boolean(preserveKeys)
    +
    +  if (Array.isArray(input)) {
    +    if (keepKeys) {
    +      const chunks: PhpAssoc<T>[] = []
    +      for (const [i, value] of input.entries()) {
    +        const chunkIndex = Math.floor(i / size)
    +        if (!chunks[chunkIndex]) {
    +          chunks[chunkIndex] = {}
    +        }
    +        chunks[chunkIndex][String(i)] = value
    +      }
    +      return chunks
    +    } else {
    +      const chunks: T[][] = []
    +      for (const [i, value] of input.entries()) {
    +        const chunkIndex = Math.floor(i / size)
    +        if (!chunks[chunkIndex]) {
    +          chunks[chunkIndex] = []
    +        }
    +        chunks[chunkIndex].push(value)
    +      }
    +      return chunks
    +    }
    +  } else {
    +    const inputEntries = Object.entries(input)
    +    if (keepKeys) {
    +      const chunks: PhpAssoc<T>[] = []
    +      for (const [i, [key, value]] of inputEntries.entries()) {
    +        const chunkIndex = Math.floor(i / size)
    +        if (!chunks[chunkIndex]) {
    +          chunks[chunkIndex] = {}
    +        }
    +        chunks[chunkIndex][key] = value
    +      }
    +      return chunks
    +    } else {
    +      const chunks: T[][] = []
    +      for (const [i, [, value]] of inputEntries.entries()) {
    +        const chunkIndex = Math.floor(i / size)
    +        if (!chunks[chunkIndex]) {
    +          chunks[chunkIndex] = []
    +        }
    +        chunks[chunkIndex].push(value)
    +      }
    +      return chunks
    +    }
    +  }
    +}
    
  • src/php/array/array_column.js+0 41 removed
    @@ -1,41 +0,0 @@
    -module.exports = function array_column(input, ColumnKey, IndexKey = null) {
    -  //  discuss at: https://locutus.io/php/array_column/
    -  // original by: Enzo Dañobeytía
    -  //   example 1: array_column([{name: 'Alex', value: 1}, {name: 'Elvis', value: 2}, {name: 'Michael', value: 3}], 'name')
    -  //   returns 1: {0: "Alex", 1: "Elvis", 2: "Michael"}
    -  //   example 2: array_column({0: {name: 'Alex', value: 1}, 1: {name: 'Elvis', value: 2}, 2: {name: 'Michael', value: 3}}, 'name')
    -  //   returns 2: {0: "Alex", 1: "Elvis", 2: "Michael"}
    -  //   example 3: array_column([{name: 'Alex', value: 1}, {name: 'Elvis', value: 2}, {name: 'Michael', value: 3}], 'name', 'value')
    -  //   returns 3: {1: "Alex", 2: "Elvis", 3: "Michael"}
    -  //   example 4: array_column([{name: 'Alex', value: 1}, {name: 'Elvis', value: 2}, {name: 'Michael', value: 3}], null, 'value')
    -  //   returns 4: {1: {name: 'Alex', value: 1}, 2: {name: 'Elvis', value: 2}, 3: {name: 'Michael', value: 3}}
    -
    -  if (input !== null && (typeof input === 'object' || Array.isArray(input))) {
    -    const newarray = []
    -    if (typeof input === 'object') {
    -      const temparray = []
    -      for (const key of Object.keys(input)) {
    -        temparray.push(input[key])
    -      }
    -      input = temparray
    -    }
    -    if (Array.isArray(input)) {
    -      for (const key of input.keys()) {
    -        if (IndexKey && input[key][IndexKey]) {
    -          if (ColumnKey) {
    -            newarray[input[key][IndexKey]] = input[key][ColumnKey]
    -          } else {
    -            newarray[input[key][IndexKey]] = input[key]
    -          }
    -        } else {
    -          if (ColumnKey) {
    -            newarray.push(input[key][ColumnKey])
    -          } else {
    -            newarray.push(input[key])
    -          }
    -        }
    -      }
    -    }
    -    return Object.assign({}, newarray)
    -  }
    -}
    
  • src/php/array/array_column.ts+51 0 added
    @@ -0,0 +1,51 @@
    +import type { PhpAssoc } from '../_helpers/_phpTypes.ts'
    +import { toPhpArrayObject } from '../_helpers/_phpTypes.ts'
    +
    +type ColumnRow<TValue> = PhpAssoc<TValue>
    +type ColumnInput<TValue> = ColumnRow<TValue>[] | PhpAssoc<ColumnRow<TValue>>
    +type ColumnOutput<TValue> = PhpAssoc<TValue | ColumnRow<TValue> | undefined>
    +
    +export function array_column<TValue>(
    +  input: ColumnInput<TValue>,
    +  columnKey: string | number | null,
    +  indexKey?: string | number | null,
    +): ColumnOutput<TValue>
    +export function array_column<TValue>(
    +  input: ColumnInput<TValue> | null | undefined,
    +  columnKey: string | number | null,
    +  indexKey?: string | number | null,
    +): ColumnOutput<TValue> | undefined {
    +  //  discuss at: https://locutus.io/php/array_column/
    +  // original by: Enzo Dañobeytía
    +  //   example 1: array_column([{name: 'Alex', value: 1}, {name: 'Elvis', value: 2}, {name: 'Michael', value: 3}], 'name')
    +  //   returns 1: {0: "Alex", 1: "Elvis", 2: "Michael"}
    +  //   example 2: array_column({0: {name: 'Alex', value: 1}, 1: {name: 'Elvis', value: 2}, 2: {name: 'Michael', value: 3}}, 'name')
    +  //   returns 2: {0: "Alex", 1: "Elvis", 2: "Michael"}
    +  //   example 3: array_column([{name: 'Alex', value: 1}, {name: 'Elvis', value: 2}, {name: 'Michael', value: 3}], 'name', 'value')
    +  //   returns 3: {1: "Alex", 2: "Elvis", 3: "Michael"}
    +  //   example 4: array_column([{name: 'Alex', value: 1}, {name: 'Elvis', value: 2}, {name: 'Michael', value: 3}], null, 'value')
    +  //   returns 4: {1: {name: 'Alex', value: 1}, 2: {name: 'Elvis', value: 2}, 3: {name: 'Michael', value: 3}}
    +
    +  if (input === null || typeof input !== 'object') {
    +    return undefined
    +  }
    +
    +  const normalizedInput = Array.isArray(input) ? input : Object.values(toPhpArrayObject<ColumnRow<TValue>>(input))
    +  const result: ColumnOutput<TValue> = {}
    +  let fallbackIndex = 0
    +
    +  for (const rowValue of normalizedInput) {
    +    const row = toPhpArrayObject<TValue>(rowValue)
    +    const indexCandidate = indexKey === null ? undefined : row[String(indexKey)]
    +
    +    const value = columnKey === null ? rowValue : row[String(columnKey)]
    +    if (indexCandidate !== undefined && indexCandidate !== null) {
    +      result[String(indexCandidate)] = value
    +    } else {
    +      result[String(fallbackIndex)] = value
    +      fallbackIndex += 1
    +    }
    +  }
    +
    +  return result
    +}
    
  • src/php/array/array_combine.js+0 40 removed
    @@ -1,40 +0,0 @@
    -module.exports = function array_combine(keys, values) {
    -  //  discuss at: https://locutus.io/php/array_combine/
    -  // original by: Kevin van Zonneveld (https://kvz.io)
    -  // improved by: Brett Zamir (https://brett-zamir.me)
    -  //   example 1: array_combine([0,1,2], ['kevin','van','zonneveld'])
    -  //   returns 1: {0: 'kevin', 1: 'van', 2: 'zonneveld'}
    -
    -  const newArray = {}
    -  let i = 0
    -
    -  // input sanitation
    -  // Only accept arrays or array-like objects
    -  // Require arrays to have a count
    -  if (typeof keys !== 'object') {
    -    return false
    -  }
    -  if (typeof values !== 'object') {
    -    return false
    -  }
    -  if (typeof keys.length !== 'number') {
    -    return false
    -  }
    -  if (typeof values.length !== 'number') {
    -    return false
    -  }
    -  if (!keys.length) {
    -    return false
    -  }
    -
    -  // number of elements does not match
    -  if (keys.length !== values.length) {
    -    return false
    -  }
    -
    -  for (i = 0; i < keys.length; i++) {
    -    newArray[keys[i]] = values[i]
    -  }
    -
    -  return newArray
    -}
    
  • src/php/array/array_combine.ts+49 0 added
    @@ -0,0 +1,49 @@
    +import type { PhpAssoc } from '../_helpers/_phpTypes.ts'
    +
    +export function array_combine<TKey extends string | number, TValue>(
    +  keys: TKey[],
    +  values: TValue[],
    +): PhpAssoc<TValue> | false {
    +  //  discuss at: https://locutus.io/php/array_combine/
    +  // original by: Kevin van Zonneveld (https://kvz.io)
    +  // improved by: Brett Zamir (https://brett-zamir.me)
    +  //   example 1: array_combine([0,1,2], ['kevin','van','zonneveld'])
    +  //   returns 1: {0: 'kevin', 1: 'van', 2: 'zonneveld'}
    +
    +  const newArray: PhpAssoc<TValue> = {}
    +  let i = 0
    +
    +  // input sanitation
    +  // Only accept arrays or array-like objects
    +  // Require arrays to have a count
    +  if (typeof keys !== 'object') {
    +    return false
    +  }
    +  if (typeof values !== 'object') {
    +    return false
    +  }
    +  if (typeof keys.length !== 'number') {
    +    return false
    +  }
    +  if (typeof values.length !== 'number') {
    +    return false
    +  }
    +  if (!keys.length) {
    +    return false
    +  }
    +
    +  // number of elements does not match
    +  if (keys.length !== values.length) {
    +    return false
    +  }
    +
    +  for (i = 0; i < keys.length; i++) {
    +    const value = values[i]
    +    if (typeof value === 'undefined') {
    +      return false
    +    }
    +    newArray[String(keys[i])] = value
    +  }
    +
    +  return newArray
    +}
    
  • src/php/array/array_count_values.js+0 57 removed
    @@ -1,57 +0,0 @@
    -module.exports = function array_count_values(array) {
    -  //      discuss at: https://locutus.io/php/array_count_values/
    -  // parity verified: PHP 8.3
    -  //     original by: Ates Goral (https://magnetiq.com)
    -  //     improved by: Michael White (https://getsprink.com)
    -  //     improved by: Kevin van Zonneveld (https://kvz.io)
    -  //        input by: sankai
    -  //        input by: Shingo
    -  //     bugfixed by: Brett Zamir (https://brett-zamir.me)
    -  //       example 1: array_count_values([ 3, 5, 3, "foo", "bar", "foo" ])
    -  //       returns 1: {3:2, 5:1, "foo":2, "bar":1}
    -  //       example 2: array_count_values({ p1: 3, p2: 5, p3: 3, p4: "foo", p5: "bar", p6: "foo" })
    -  //       returns 2: {3:2, 5:1, "foo":2, "bar":1}
    -  //       example 3: array_count_values([ true, 4.2, 42, "fubar" ])
    -  //       returns 3: {42:1, "fubar":1}
    -
    -  const tmpArr = {}
    -  let key = ''
    -  let t = ''
    -
    -  const _getType = function (obj) {
    -    // Objects are php associative arrays.
    -    let t = typeof obj
    -    t = t.toLowerCase()
    -    if (t === 'object') {
    -      t = 'array'
    -    }
    -    return t
    -  }
    -
    -  const _countValue = function (tmpArr, value) {
    -    if (typeof value === 'number') {
    -      if (Math.floor(value) !== value) {
    -        return
    -      }
    -    } else if (typeof value !== 'string') {
    -      return
    -    }
    -
    -    if (value in tmpArr && tmpArr.hasOwnProperty(value)) {
    -      ++tmpArr[value]
    -    } else {
    -      tmpArr[value] = 1
    -    }
    -  }
    -
    -  t = _getType(array)
    -  if (t === 'array') {
    -    for (key in array) {
    -      if (array.hasOwnProperty(key)) {
    -        _countValue.call(this, tmpArr, array[key])
    -      }
    -    }
    -  }
    -
    -  return tmpArr
    -}
    
  • src/php/array/array_count_values.ts+61 0 added
    @@ -0,0 +1,61 @@
    +import {
    +  isObjectLike,
    +  type PhpArrayLike,
    +  type PhpAssoc,
    +  type PhpRuntimeValue,
    +  toPhpArrayObject,
    +} from '../_helpers/_phpTypes.ts'
    +
    +type CountValue = PhpRuntimeValue
    +
    +export function array_count_values(array: PhpArrayLike<CountValue>): PhpAssoc<number> {
    +  //      discuss at: https://locutus.io/php/array_count_values/
    +  // parity verified: PHP 8.3
    +  //     original by: Ates Goral (https://magnetiq.com)
    +  //     improved by: Michael White (https://getsprink.com)
    +  //     improved by: Kevin van Zonneveld (https://kvz.io)
    +  //        input by: sankai
    +  //        input by: Shingo
    +  //     bugfixed by: Brett Zamir (https://brett-zamir.me)
    +  //       example 1: array_count_values([ 3, 5, 3, "foo", "bar", "foo" ])
    +  //       returns 1: {3:2, 5:1, "foo":2, "bar":1}
    +  //       example 2: array_count_values({ p1: 3, p2: 5, p3: 3, p4: "foo", p5: "bar", p6: "foo" })
    +  //       returns 2: {3:2, 5:1, "foo":2, "bar":1}
    +  //       example 3: array_count_values([ true, 4.2, 42, "fubar" ])
    +  //       returns 3: {42:1, "fubar":1}
    +
    +  const tmpArr: PhpAssoc<number> = {}
    +
    +  const _countValue = function (target: PhpAssoc<number>, value: CountValue): void {
    +    let normalized = ''
    +    if (typeof value === 'number') {
    +      if (Math.floor(value) !== value) {
    +        return
    +      }
    +      normalized = String(value)
    +    } else if (typeof value !== 'string') {
    +      return
    +    } else {
    +      normalized = value
    +    }
    +
    +    if (normalized in target && Object.prototype.hasOwnProperty.call(target, normalized)) {
    +      target[normalized] = (target[normalized] ?? 0) + 1
    +    } else {
    +      target[normalized] = 1
    +    }
    +  }
    +
    +  if (!isObjectLike(array)) {
    +    return tmpArr
    +  }
    +
    +  const source = toPhpArrayObject<CountValue>(array)
    +  for (const key in source) {
    +    if (Object.prototype.hasOwnProperty.call(source, key)) {
    +      _countValue(tmpArr, source[key])
    +    }
    +  }
    +
    +  return tmpArr
    +}
    
  • src/php/array/array_diff_assoc.js+0 30 removed
    @@ -1,30 +0,0 @@
    -module.exports = function array_diff_assoc(arr1) {
    -  //  discuss at: https://locutus.io/php/array_diff_assoc/
    -  // original by: Kevin van Zonneveld (https://kvz.io)
    -  // bugfixed by: 0m3r
    -  //  revised by: Brett Zamir (https://brett-zamir.me)
    -  //   example 1: array_diff_assoc({0: 'Kevin', 1: 'van', 2: 'Zonneveld'}, {0: 'Kevin', 4: 'van', 5: 'Zonneveld'})
    -  //   returns 1: {1: 'van', 2: 'Zonneveld'}
    -
    -  const retArr = {}
    -  const argl = arguments.length
    -  let k1 = ''
    -  let i = 1
    -  let k = ''
    -  let arr = {}
    -
    -  arr1keys: for (k1 in arr1) {
    -    for (i = 1; i < argl; i++) {
    -      arr = arguments[i]
    -      for (k in arr) {
    -        if (arr[k] === arr1[k1] && k === k1) {
    -          // If it reaches here, it was found in at least one array, so try next value
    -          continue arr1keys
    -        }
    -      }
    -      retArr[k1] = arr1[k1]
    -    }
    -  }
    -
    -  return retArr
    -}
    
  • src/php/array/array_diff_assoc.ts+34 0 added
    @@ -0,0 +1,34 @@
    +import { entriesOfPhpAssoc, type PhpArrayLike, type PhpAssoc, toPhpArrayObject } from '../_helpers/_phpTypes.ts'
    +
    +export function array_diff_assoc<TValue>(
    +  arr1: PhpArrayLike<TValue>,
    +  ...arrays: Array<PhpArrayLike<TValue>>
    +): PhpAssoc<TValue> {
    +  //  discuss at: https://locutus.io/php/array_diff_assoc/
    +  // original by: Kevin van Zonneveld (https://kvz.io)
    +  // bugfixed by: 0m3r
    +  //  revised by: Brett Zamir (https://brett-zamir.me)
    +  //   example 1: array_diff_assoc({0: 'Kevin', 1: 'van', 2: 'Zonneveld'}, {0: 'Kevin', 4: 'van', 5: 'Zonneveld'})
    +  //   returns 1: {1: 'van', 2: 'Zonneveld'}
    +
    +  const retArr: PhpAssoc<TValue> = {}
    +
    +  if (arrays.length < 1) {
    +    return retArr
    +  }
    +
    +  const arr1Object = toPhpArrayObject<TValue>(arr1)
    +  arr1keys: for (const [k1, arr1Value] of entriesOfPhpAssoc(arr1Object)) {
    +    for (const nextArray of arrays) {
    +      const arr = toPhpArrayObject<TValue>(nextArray)
    +      for (const [k, value] of entriesOfPhpAssoc(arr)) {
    +        if (value === arr1Value && k === k1) {
    +          continue arr1keys
    +        }
    +      }
    +    }
    +    retArr[k1] = arr1Value
    +  }
    +
    +  return retArr
    +}
    
  • src/php/array/array_diff.js+0 30 removed
    @@ -1,30 +0,0 @@
    -module.exports = function array_diff(arr1) {
    -  //  discuss at: https://locutus.io/php/array_diff/
    -  // original by: Kevin van Zonneveld (https://kvz.io)
    -  // improved by: Sanjoy Roy
    -  //  revised by: Brett Zamir (https://brett-zamir.me)
    -  //   example 1: array_diff(['Kevin', 'van', 'Zonneveld'], ['van', 'Zonneveld'])
    -  //   returns 1: {0:'Kevin'}
    -
    -  const retArr = {}
    -  const argl = arguments.length
    -  let k1 = ''
    -  let i = 1
    -  let k = ''
    -  let arr = {}
    -
    -  arr1keys: for (k1 in arr1) {
    -    for (i = 1; i < argl; i++) {
    -      arr = arguments[i]
    -      for (k in arr) {
    -        if (arr[k] === arr1[k1]) {
    -          // If it reaches here, it was found in at least one array, so try next value
    -          continue arr1keys
    -        }
    -      }
    -      retArr[k1] = arr1[k1]
    -    }
    -  }
    -
    -  return retArr
    -}
    
  • src/php/array/array_diff_key.js+0 32 removed
    @@ -1,32 +0,0 @@
    -module.exports = function array_diff_key(arr1) {
    -  //  discuss at: https://locutus.io/php/array_diff_key/
    -  // original by: Ates Goral (https://magnetiq.com)
    -  //  revised by: Brett Zamir (https://brett-zamir.me)
    -  //    input by: Everlasto
    -  //   example 1: array_diff_key({red: 1, green: 2, blue: 3, white: 4}, {red: 5})
    -  //   returns 1: {"green":2, "blue":3, "white":4}
    -  //   example 2: array_diff_key({red: 1, green: 2, blue: 3, white: 4}, {red: 5}, {red: 5})
    -  //   returns 2: {"green":2, "blue":3, "white":4}
    -
    -  const argl = arguments.length
    -  const retArr = {}
    -  let k1 = ''
    -  let i = 1
    -  let k = ''
    -  let arr = {}
    -
    -  arr1keys: for (k1 in arr1) {
    -    for (i = 1; i < argl; i++) {
    -      arr = arguments[i]
    -      for (k in arr) {
    -        if (k === k1) {
    -          // If it reaches here, it was found in at least one array, so try next value
    -          continue arr1keys
    -        }
    -      }
    -      retArr[k1] = arr1[k1]
    -    }
    -  }
    -
    -  return retArr
    -}
    
  • src/php/array/array_diff_key.ts+36 0 added
    @@ -0,0 +1,36 @@
    +import { entriesOfPhpAssoc, type PhpArrayLike, type PhpAssoc, toPhpArrayObject } from '../_helpers/_phpTypes.ts'
    +
    +export function array_diff_key<TValue>(
    +  arr1: PhpArrayLike<TValue>,
    +  ...arrays: Array<PhpArrayLike<TValue>>
    +): PhpAssoc<TValue> {
    +  //  discuss at: https://locutus.io/php/array_diff_key/
    +  // original by: Ates Goral (https://magnetiq.com)
    +  //  revised by: Brett Zamir (https://brett-zamir.me)
    +  //    input by: Everlasto
    +  //   example 1: array_diff_key({red: 1, green: 2, blue: 3, white: 4}, {red: 5})
    +  //   returns 1: {"green":2, "blue":3, "white":4}
    +  //   example 2: array_diff_key({red: 1, green: 2, blue: 3, white: 4}, {red: 5}, {red: 5})
    +  //   returns 2: {"green":2, "blue":3, "white":4}
    +
    +  const retArr: PhpAssoc<TValue> = {}
    +
    +  if (arrays.length < 1) {
    +    return retArr
    +  }
    +
    +  const arr1Object = toPhpArrayObject<TValue>(arr1)
    +  arr1keys: for (const [k1, arr1Value] of entriesOfPhpAssoc(arr1Object)) {
    +    for (const nextArray of arrays) {
    +      const arr = toPhpArrayObject<TValue>(nextArray)
    +      for (const [k] of entriesOfPhpAssoc(arr)) {
    +        if (k === k1) {
    +          continue arr1keys
    +        }
    +      }
    +    }
    +    retArr[k1] = arr1Value
    +  }
    +
    +  return retArr
    +}
    
  • src/php/array/array_diff.ts+34 0 added
    @@ -0,0 +1,34 @@
    +import { entriesOfPhpAssoc, type PhpArrayLike, type PhpAssoc, toPhpArrayObject } from '../_helpers/_phpTypes.ts'
    +
    +export function array_diff<TValue>(
    +  arr1: PhpArrayLike<TValue>,
    +  ...arrays: Array<PhpArrayLike<TValue>>
    +): PhpAssoc<TValue> {
    +  //  discuss at: https://locutus.io/php/array_diff/
    +  // original by: Kevin van Zonneveld (https://kvz.io)
    +  // improved by: Sanjoy Roy
    +  //  revised by: Brett Zamir (https://brett-zamir.me)
    +  //   example 1: array_diff(['Kevin', 'van', 'Zonneveld'], ['van', 'Zonneveld'])
    +  //   returns 1: {0:'Kevin'}
    +
    +  const retArr: PhpAssoc<TValue> = {}
    +
    +  if (arrays.length < 1) {
    +    return retArr
    +  }
    +
    +  const arr1Object = toPhpArrayObject<TValue>(arr1)
    +  arr1keys: for (const [k1, arr1Value] of entriesOfPhpAssoc(arr1Object)) {
    +    for (const nextArray of arrays) {
    +      const arr = toPhpArrayObject<TValue>(nextArray)
    +      for (const [, value] of entriesOfPhpAssoc(arr)) {
    +        if (value === arr1Value) {
    +          continue arr1keys
    +        }
    +      }
    +    }
    +    retArr[k1] = arr1Value
    +  }
    +
    +  return retArr
    +}
    
  • src/php/array/array_diff_uassoc.js+0 35 removed
    @@ -1,35 +0,0 @@
    -module.exports = function array_diff_uassoc(arr1) {
    -  //  discuss at: https://locutus.io/php/array_diff_uassoc/
    -  // original by: Brett Zamir (https://brett-zamir.me)
    -  //   example 1: var $array1 = {a: 'green', b: 'brown', c: 'blue', 0: 'red'}
    -  //   example 1: var $array2 = {a: 'GREEN', B: 'brown', 0: 'yellow', 1: 'red'}
    -  //   example 1: array_diff_uassoc($array1, $array2, function (key1, key2) { return (key1 === key2 ? 0 : (key1 > key2 ? 1 : -1)) })
    -  //   returns 1: {a: 'green', b: 'brown', c: 'blue', 0: 'red'}
    -
    -  const retArr = {}
    -  const arglm1 = arguments.length - 1
    -  let cb = arguments[arglm1]
    -  let arr = {}
    -  let i = 1
    -  let k1 = ''
    -  let k = ''
    -
    -  const $global = typeof window !== 'undefined' ? window : global
    -
    -  cb = typeof cb === 'string' ? $global[cb] : Array.isArray(cb) ? $global[cb[0]][cb[1]] : cb
    -
    -  arr1keys: for (k1 in arr1) {
    -    for (i = 1; i < arglm1; i++) {
    -      arr = arguments[i]
    -      for (k in arr) {
    -        if (arr[k] === arr1[k1] && cb(k, k1) === 0) {
    -          // If it reaches here, it was found in at least one array, so try next value
    -          continue arr1keys
    -        }
    -      }
    -      retArr[k1] = arr1[k1]
    -    }
    -  }
    -
    -  return retArr
    -}
    
  • src/php/array/array_diff_uassoc.ts+44 0 added
    @@ -0,0 +1,44 @@
    +import { resolveNumericComparator } from '../_helpers/_callbackResolver.ts'
    +import {
    +  entriesOfPhpAssoc,
    +  isPhpCallableDescriptor,
    +  type PhpAssoc,
    +  type PhpKeyComparatorDescriptor,
    +  toPhpArrayObject,
    +} from '../_helpers/_phpTypes.ts'
    +
    +type DiffArray<T> = PhpAssoc<T> | T[]
    +
    +export function array_diff_uassoc<T>(
    +  arr1: PhpAssoc<T>,
    +  ...arraysAndCallback: [arr2: DiffArray<T>, ...rest: Array<DiffArray<T>>, callback: PhpKeyComparatorDescriptor]
    +): PhpAssoc<T> {
    +  //  discuss at: https://locutus.io/php/array_diff_uassoc/
    +  // original by: Brett Zamir (https://brett-zamir.me)
    +  //   example 1: var $array1 = {a: 'green', b: 'brown', c: 'blue', 0: 'red'}
    +  //   example 1: var $array2 = {a: 'GREEN', B: 'brown', 0: 'yellow', 1: 'red'}
    +  //   example 1: array_diff_uassoc($array1, $array2, function (key1, key2) { return (key1 === key2 ? 0 : (key1 > key2 ? 1 : -1)) })
    +  //   returns 1: {a: 'green', b: 'brown', c: 'blue', 0: 'red'}
    +
    +  const retArr: PhpAssoc<T> = {}
    +  const callback = arraysAndCallback[arraysAndCallback.length - 1]
    +  if (typeof callback === 'undefined' || !isPhpCallableDescriptor<[string, string]>(callback)) {
    +    throw new Error('array_diff_uassoc(): Invalid callback')
    +  }
    +  const arrays = arraysAndCallback.slice(0, -1).map((value) => toPhpArrayObject<T>(value))
    +  const keyComparator = resolveNumericComparator<string, string>(callback, 'array_diff_uassoc(): Invalid callback')
    +
    +  arr1keys: for (const [k1, arr1Value] of entriesOfPhpAssoc(arr1)) {
    +    for (const arr of arrays) {
    +      for (const [k, arrValue] of entriesOfPhpAssoc(arr)) {
    +        if (arrValue === arr1Value && keyComparator(k, k1) === 0) {
    +          // If it reaches here, it was found in at least one array, so try next value
    +          continue arr1keys
    +        }
    +      }
    +    }
    +    retArr[k1] = arr1Value
    +  }
    +
    +  return retArr
    +}
    
  • src/php/array/array_diff_ukey.js+0 36 removed
    @@ -1,36 +0,0 @@
    -module.exports = function array_diff_ukey(arr1) {
    -  //  discuss at: https://locutus.io/php/array_diff_ukey/
    -  // original by: Brett Zamir (https://brett-zamir.me)
    -  //   example 1: var $array1 = {blue: 1, red: 2, green: 3, purple: 4}
    -  //   example 1: var $array2 = {green: 5, blue: 6, yellow: 7, cyan: 8}
    -  //   example 1: array_diff_ukey($array1, $array2, function (key1, key2){ return (key1 === key2 ? 0 : (key1 > key2 ? 1 : -1)); })
    -  //   returns 1: {red: 2, purple: 4}
    -
    -  const retArr = {}
    -  const arglm1 = arguments.length - 1
    -  // var arglm2 = arglm1 - 1
    -  let cb = arguments[arglm1]
    -  let k1 = ''
    -  let i = 1
    -  let arr = {}
    -  let k = ''
    -
    -  const $global = typeof window !== 'undefined' ? window : global
    -
    -  cb = typeof cb === 'string' ? $global[cb] : Array.isArray(cb) ? $global[cb[0]][cb[1]] : cb
    -
    -  arr1keys: for (k1 in arr1) {
    -    for (i = 1; i < arglm1; i++) {
    -      arr = arguments[i]
    -      for (k in arr) {
    -        if (cb(k, k1) === 0) {
    -          // If it reaches here, it was found in at least one array, so try next value
    -          continue arr1keys
    -        }
    -      }
    -      retArr[k1] = arr1[k1]
    -    }
    -  }
    -
    -  return retArr
    -}
    
  • src/php/array/array_diff_ukey.ts+44 0 added
    @@ -0,0 +1,44 @@
    +import { resolveNumericComparator } from '../_helpers/_callbackResolver.ts'
    +import {
    +  entriesOfPhpAssoc,
    +  isPhpCallableDescriptor,
    +  type PhpAssoc,
    +  type PhpKeyComparatorDescriptor,
    +  toPhpArrayObject,
    +} from '../_helpers/_phpTypes.ts'
    +
    +type DiffArray<T> = PhpAssoc<T> | T[]
    +
    +export function array_diff_ukey<T>(
    +  arr1: PhpAssoc<T>,
    +  ...arraysAndCallback: [arr2: DiffArray<T>, ...rest: Array<DiffArray<T>>, callback: PhpKeyComparatorDescriptor]
    +): PhpAssoc<T> {
    +  //  discuss at: https://locutus.io/php/array_diff_ukey/
    +  // original by: Brett Zamir (https://brett-zamir.me)
    +  //   example 1: var $array1 = {blue: 1, red: 2, green: 3, purple: 4}
    +  //   example 1: var $array2 = {green: 5, blue: 6, yellow: 7, cyan: 8}
    +  //   example 1: array_diff_ukey($array1, $array2, function (key1, key2){ return (key1 === key2 ? 0 : (key1 > key2 ? 1 : -1)); })
    +  //   returns 1: {red: 2, purple: 4}
    +
    +  const retArr: PhpAssoc<T> = {}
    +  const callback = arraysAndCallback[arraysAndCallback.length - 1]
    +  if (typeof callback === 'undefined' || !isPhpCallableDescriptor<[string, string]>(callback)) {
    +    throw new Error('array_diff_ukey(): Invalid callback')
    +  }
    +  const arrays = arraysAndCallback.slice(0, -1).map((value) => toPhpArrayObject<T>(value))
    +  const keyComparator = resolveNumericComparator<string, string>(callback, 'array_diff_ukey(): Invalid callback')
    +
    +  arr1keys: for (const [k1, arr1Value] of entriesOfPhpAssoc(arr1)) {
    +    for (const arr of arrays) {
    +      for (const [k] of entriesOfPhpAssoc(arr)) {
    +        if (keyComparator(k, k1) === 0) {
    +          // If it reaches here, it was found in at least one array, so try next value
    +          continue arr1keys
    +        }
    +      }
    +    }
    +    retArr[k1] = arr1Value
    +  }
    +
    +  return retArr
    +}
    
  • src/php/array/array_fill.js+0 19 removed
    @@ -1,19 +0,0 @@
    -module.exports = function array_fill(startIndex, num, mixedVal) {
    -  //      discuss at: https://locutus.io/php/array_fill/
    -  // parity verified: PHP 8.3
    -  //     original by: Kevin van Zonneveld (https://kvz.io)
    -  //     improved by: Waldo Malqui Silva (https://waldo.malqui.info)
    -  //       example 1: array_fill(5, 6, 'banana')
    -  //       returns 1: { 5: 'banana', 6: 'banana', 7: 'banana', 8: 'banana', 9: 'banana', 10: 'banana' }
    -
    -  let key
    -  const tmpArr = {}
    -
    -  if (!isNaN(startIndex) && !isNaN(num)) {
    -    for (key = 0; key < num; key++) {
    -      tmpArr[key + startIndex] = mixedVal
    -    }
    -  }
    -
    -  return tmpArr
    -}
    
  • src/php/array/array_fill_keys.js+0 17 removed
    @@ -1,17 +0,0 @@
    -module.exports = function array_fill_keys(keys, value) {
    -  //  discuss at: https://locutus.io/php/array_fill_keys/
    -  // original by: Brett Zamir (https://brett-zamir.me)
    -  // bugfixed by: Brett Zamir (https://brett-zamir.me)
    -  //   example 1: var $keys = {'a': 'foo', 2: 5, 3: 10, 4: 'bar'}
    -  //   example 1: array_fill_keys($keys, 'banana')
    -  //   returns 1: {"foo": "banana", 5: "banana", 10: "banana", "bar": "banana"}
    -
    -  const retObj = {}
    -  let key = ''
    -
    -  for (key in keys) {
    -    retObj[keys[key]] = value
    -  }
    -
    -  return retObj
    -}
    
  • src/php/array/array_fill_keys.ts+28 0 added
    @@ -0,0 +1,28 @@
    +import {
    +  type PhpArrayLike,
    +  type PhpAssoc,
    +  type PhpKey,
    +  type StringLike,
    +  toPhpArrayObject,
    +} from '../_helpers/_phpTypes.ts'
    +
    +export function array_fill_keys<TKey extends PhpKey | StringLike, TValue>(
    +  keys: PhpArrayLike<TKey>,
    +  value: TValue,
    +): PhpAssoc<TValue> {
    +  //  discuss at: https://locutus.io/php/array_fill_keys/
    +  // original by: Brett Zamir (https://brett-zamir.me)
    +  // bugfixed by: Brett Zamir (https://brett-zamir.me)
    +  //   example 1: var $keys = {'a': 'foo', 2: 5, 3: 10, 4: 'bar'}
    +  //   example 1: array_fill_keys($keys, 'banana')
    +  //   returns 1: {"foo": "banana", 5: "banana", 10: "banana", "bar": "banana"}
    +
    +  const retObj: PhpAssoc<TValue> = {}
    +  const keyedValues = toPhpArrayObject<TKey>(keys)
    +
    +  for (const key in keyedValues) {
    +    retObj[String(keyedValues[key])] = value
    +  }
    +
    +  return retObj
    +}
    
  • src/php/array/array_fill.ts+19 0 added
    @@ -0,0 +1,19 @@
    +import type { PhpAssoc } from '../_helpers/_phpTypes.ts'
    +
    +export function array_fill<TValue>(startIndex: number, num: number, mixedVal: TValue): PhpAssoc<TValue> {
    +  //      discuss at: https://locutus.io/php/array_fill/
    +  // parity verified: PHP 8.3
    +  //     original by: Kevin van Zonneveld (https://kvz.io)
    +  //     improved by: Waldo Malqui Silva (https://waldo.malqui.info)
    +  //       example 1: array_fill(5, 6, 'banana')
    +  //       returns 1: { 5: 'banana', 6: 'banana', 7: 'banana', 8: 'banana', 9: 'banana', 10: 'banana' }
    +
    +  const tmpArr: PhpAssoc<TValue> = {}
    +  if (!isNaN(startIndex) && !isNaN(num)) {
    +    for (let key = 0; key < num; key++) {
    +      tmpArr[String(key + startIndex)] = mixedVal
    +    }
    +  }
    +
    +  return tmpArr
    +}
    
  • src/php/array/array_filter.js+0 37 removed
    @@ -1,37 +0,0 @@
    -module.exports = function array_filter(arr, func) {
    -  //  discuss at: https://locutus.io/php/array_filter/
    -  // original by: Brett Zamir (https://brett-zamir.me)
    -  //    input by: max4ever
    -  // improved by: Brett Zamir (https://brett-zamir.me)
    -  //      note 1: Takes a function as an argument, not a function's name
    -  //   example 1: var odd = function (num) {return (num & 1);}
    -  //   example 1: array_filter({"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}, odd)
    -  //   returns 1: {"a": 1, "c": 3, "e": 5}
    -  //   example 2: var even = function (num) {return (!(num & 1));}
    -  //   example 2: array_filter([6, 7, 8, 9, 10, 11, 12], even)
    -  //   returns 2: [ 6, , 8, , 10, , 12 ]
    -  //   example 3: array_filter({"a": 1, "b": false, "c": -1, "d": 0, "e": null, "f":'', "g":undefined})
    -  //   returns 3: {"a":1, "c":-1}
    -
    -  let retObj = {}
    -  let k
    -
    -  func =
    -    func ||
    -    function (v) {
    -      return v
    -    }
    -
    -  // @todo: Issue #73
    -  if (Array.isArray(arr)) {
    -    retObj = []
    -  }
    -
    -  for (k in arr) {
    -    if (func(arr[k])) {
    -      retObj[k] = arr[k]
    -    }
    -  }
    -
    -  return retObj
    -}
    
  • src/php/_helpers/_arrayPointers.ts+82 0 added
    @@ -0,0 +1,82 @@
    +import { ensurePhpRuntimeState } from './_phpRuntimeState.ts'
    +import { entriesOfPhpAssoc, type PhpArrayLike, type PhpInput, type PhpList } from './_phpTypes.ts'
    +
    +interface PointerState {
    +  cursor: number
    +  setCursor: (nextCursor: number) => void
    +}
    +
    +const findPointerIndex = (pointers: PhpList<PhpInput>, target: PhpInput): number => {
    +  for (let index = 0; index < pointers.length; index += 1) {
    +    if (pointers[index] === target) {
    +      return index
    +    }
    +  }
    +
    +  return -1
    +}
    +
    +export function getPointerState<T>(target: PhpArrayLike<T>, initialize = true): PointerState | null {
    +  const runtime = ensurePhpRuntimeState()
    +  const pointers = runtime.pointers
    +  const pointerTarget: PhpInput = target
    +
    +  let index = findPointerIndex(pointers, pointerTarget)
    +  if (index === -1) {
    +    if (!initialize) {
    +      return null
    +    }
    +    pointers.push(pointerTarget, 0)
    +    index = pointers.length - 2
    +  }
    +
    +  const cursorValue = pointers[index + 1]
    +  const cursor = typeof cursorValue === 'number' ? cursorValue : 0
    +
    +  return {
    +    cursor,
    +    setCursor: (nextCursor: number) => {
    +      pointers[index + 1] = nextCursor
    +    },
    +  }
    +}
    +
    +export function getArrayLikeLength<T>(target: PhpArrayLike<T>): number {
    +  if (Array.isArray(target)) {
    +    return target.length
    +  }
    +
    +  let count = 0
    +  for (const _key in target) {
    +    count += 1
    +  }
    +  return count
    +}
    +
    +export function getEntryAtCursor<T>(target: PhpArrayLike<T>, cursor: number): [string | number, T] | null {
    +  if (cursor < 0) {
    +    return null
    +  }
    +
    +  if (Array.isArray(target)) {
    +    if (cursor >= target.length) {
    +      return null
    +    }
    +
    +    const value = target[cursor]
    +    if (typeof value === 'undefined') {
    +      return null
    +    }
    +    return [cursor, value]
    +  }
    +
    +  let index = 0
    +  for (const [key, value] of entriesOfPhpAssoc(target)) {
    +    if (index === cursor) {
    +      return [key, value]
    +    }
    +    index += 1
    +  }
    +
    +  return null
    +}
    
  • src/php/_helpers/_bc.js+0 1322 removed
    @@ -1,1322 +0,0 @@
    -// SPDX-License-Identifier: LGPL-2.1-or-later
    -// NOTE: This file is LGPL licensed, not MIT like the rest of locutus.
    -// See LICENSE file for details.
    -
    -module.exports = function _bc() {
    -  //  discuss at: https://locutus.io/php/_helpers/_bc
    -  // original by: lmeyrick (https://sourceforge.net/projects/bcmath-js/)
    -  // improved by: Brett Zamir (https://brett-zamir.me)
    -  //   example 1: var $bc = _bc()
    -  //   example 1: var $result = $bc.PLUS
    -  //   returns 1: '+'
    -
    -  /**
    -   * BC Math Library for Javascript (LGPL-2.1)
    -   * Ported from the PHP5 bcmath extension source code,
    -   * which uses the Libbcmath package...
    -   *    Copyright (C) 1991, 1992, 1993, 1994, 1997 Free Software Foundation, Inc.
    -   *    Copyright (C) 2000 Philip A. Nelson
    -   *     The Free Software Foundation, Inc.
    -   *     59 Temple Place, Suite 330
    -   *     Boston, MA 02111-1307 USA.
    -   *      e-mail:  philnelson@acm.org
    -   *     us-mail:  Philip A. Nelson
    -   *               Computer Science Department, 9062
    -   *               Western Washington University
    -   *               Bellingham, WA 98226-9062
    -   *
    -   * bcmath-js homepage: https://sourceforge.net/projects/bcmath-js/
    -   *
    -   * This code is covered under the LGPL-2.1 license.
    -   */
    -
    -  /**
    -   * Binary Calculator (BC) Arbitrary Precision Mathematics Lib v0.10  (LGPL)
    -   * Copy of Libbcmath included in PHP5 src
    -   *
    -   * Note: this is just the shared library file and does not include the php-style functions.
    -   *       use bcmath{-min}.js for functions like bcadd, bcsub etc.
    -   *
    -   * Feel free to use how-ever you want, just email any bug-fixes/improvements
    -   * to the sourceforge project:
    -   *
    -   *
    -   * Ported from the PHP5 bcmath extension source code,
    -   * which uses the Libbcmath package...
    -   *    Copyright (C) 1991, 1992, 1993, 1994, 1997 Free Software Foundation, Inc.
    -   *    Copyright (C) 2000 Philip A. Nelson
    -   *     The Free Software Foundation, Inc.
    -   *     59 Temple Place, Suite 330
    -   *     Boston, MA 02111-1307 USA.
    -   *      e-mail:  philnelson@acm.org
    -   *     us-mail:  Philip A. Nelson
    -   *               Computer Science Department, 9062
    -   *               Western Washington University
    -   *               Bellingham, WA 98226-9062
    -   */
    -
    -  const Libbcmath = {
    -    PLUS: '+',
    -    MINUS: '-',
    -    BASE: 10,
    -    // must be 10 (for now)
    -    scale: 0,
    -    // default scale
    -    /**
    -     * Basic number structure
    -     */
    -    bc_num: function () {
    -      this.n_sign = null // sign
    -      this.n_len = null // (int) The number of digits before the decimal point.
    -      this.n_scale = null // (int) The number of digits after the decimal point.
    -      // this.n_refs = null; // (int) The number of pointers to this number.
    -      // this.n_text = null; // ?? Linked list for available list.
    -      this.n_value = null // array as value, where 1.23 = [1,2,3]
    -      this.toString = function () {
    -        let r
    -        let tmp
    -        tmp = this.n_value.join('')
    -
    -        // add minus sign (if applicable) then add the integer part
    -        r = (this.n_sign === Libbcmath.PLUS ? '' : this.n_sign) + tmp.substr(0, this.n_len)
    -
    -        // if decimal places, add a . and the decimal part
    -        if (this.n_scale > 0) {
    -          r += '.' + tmp.substr(this.n_len, this.n_scale)
    -        }
    -        return r
    -      }
    -    },
    -
    -    /**
    -     * Base add function
    -     *
    -     //  Here is the full add routine that takes care of negative numbers.
    -     //  N1 is added to N2 and the result placed into RESULT.  SCALE_MIN
    -     //  is the minimum scale for the result.
    -     *
    -     * @param {bc_num} n1
    -     * @param {bc_num} n2
    -     * @param {int} scaleMin
    -     * @return bc_num
    -     */
    -    bc_add: function (n1, n2, scaleMin) {
    -      let sum
    -      let cmpRes
    -      let resScale
    -
    -      if (n1.n_sign === n2.n_sign) {
    -        sum = Libbcmath._bc_do_add(n1, n2, scaleMin)
    -        sum.n_sign = n1.n_sign
    -      } else {
    -        // subtraction must be done.
    -        cmpRes = Libbcmath._bc_do_compare(n1, n2, false, false) // Compare magnitudes.
    -        switch (cmpRes) {
    -          case -1:
    -            // n1 is less than n2, subtract n1 from n2.
    -            sum = Libbcmath._bc_do_sub(n2, n1, scaleMin)
    -            sum.n_sign = n2.n_sign
    -            break
    -
    -          case 0:
    -            // They are equal! return zero with the correct scale!
    -            resScale = Libbcmath.MAX(scaleMin, Libbcmath.MAX(n1.n_scale, n2.n_scale))
    -            sum = Libbcmath.bc_new_num(1, resScale)
    -            Libbcmath.memset(sum.n_value, 0, 0, resScale + 1)
    -            break
    -
    -          case 1:
    -            // n2 is less than n1, subtract n2 from n1.
    -            sum = Libbcmath._bc_do_sub(n1, n2, scaleMin)
    -            sum.n_sign = n1.n_sign
    -        }
    -      }
    -      return sum
    -    },
    -
    -    /**
    -     * This is the "user callable" routine to compare numbers N1 and N2.
    -     * @param {bc_num} n1
    -     * @param {bc_num} n2
    -     * @return int -1, 0, 1  (n1 < n2, ===, n1 > n2)
    -     */
    -    bc_compare: function (n1, n2) {
    -      return Libbcmath._bc_do_compare(n1, n2, true, false)
    -    },
    -
    -    _one_mult: function (num, nPtr, size, digit, result, rPtr) {
    -      let carry
    -      let value // int
    -      let nptr
    -      let rptr // int pointers
    -      if (digit === 0) {
    -        Libbcmath.memset(result, 0, 0, size) // memset (result, 0, size);
    -      } else {
    -        if (digit === 1) {
    -          Libbcmath.memcpy(result, rPtr, num, nPtr, size) // memcpy (result, num, size);
    -        } else {
    -          // Initialize
    -          nptr = nPtr + size - 1 // nptr = (unsigned char *) (num+size-1);
    -          rptr = rPtr + size - 1 // rptr = (unsigned char *) (result+size-1);
    -          carry = 0
    -
    -          while (size-- > 0) {
    -            value = num[nptr--] * digit + carry // value = *nptr-- * digit + carry;
    -            result[rptr--] = value % Libbcmath.BASE // @CHECK cint //*rptr-- = value % BASE;
    -            carry = Math.floor(value / Libbcmath.BASE) // @CHECK cint //carry = value / BASE;
    -          }
    -
    -          if (carry !== 0) {
    -            result[rptr] = carry
    -          }
    -        }
    -      }
    -    },
    -
    -    bc_divide: function (n1, n2, scale) {
    -      // var quot // bc_num return
    -      let qval // bc_num
    -      let num1
    -      let num2 // string
    -      let ptr1
    -      let ptr2
    -      let n2ptr
    -      let qptr // int pointers
    -      let scale1
    -      let val // int
    -      let len1
    -      let len2
    -      let scale2
    -      let qdigits
    -      let extra
    -      let count // int
    -      let qdig
    -      let qguess
    -      let borrow
    -      let carry // int
    -      let mval // string
    -      let zero // char
    -      let norm // int
    -      // var ptrs // return object from one_mul
    -      // Test for divide by zero. (return failure)
    -      if (Libbcmath.bc_is_zero(n2)) {
    -        return -1
    -      }
    -
    -      // Test for zero divide by anything (return zero)
    -      if (Libbcmath.bc_is_zero(n1)) {
    -        return Libbcmath.bc_new_num(1, scale)
    -      }
    -
    -      /* Test for n1 equals n2 (return 1 as n1 nor n2 are zero)
    -        if (Libbcmath.bc_compare(n1, n2, Libbcmath.MAX(n1.n_scale, n2.n_scale)) === 0) {
    -          quot=Libbcmath.bc_new_num(1, scale);
    -          quot.n_value[0] = 1;
    -          return quot;
    -        }
    -      */
    -
    -      // Test for divide by 1.  If it is we must truncate.
    -      // @todo: check where scale > 0 too.. can't see why not
    -      // (ie bc_is_zero - add bc_is_one function)
    -      if (n2.n_scale === 0) {
    -        if (n2.n_len === 1 && n2.n_value[0] === 1) {
    -          qval = Libbcmath.bc_new_num(n1.n_len, scale) // qval = bc_new_num (n1->n_len, scale);
    -          qval.n_sign = n1.n_sign === n2.n_sign ? Libbcmath.PLUS : Libbcmath.MINUS
    -          // memset (&qval->n_value[n1->n_len],0,scale):
    -          Libbcmath.memset(qval.n_value, n1.n_len, 0, scale)
    -          // memcpy (qval->n_value, n1->n_value, n1->n_len + MIN(n1->n_scale,scale)):
    -          Libbcmath.memcpy(qval.n_value, 0, n1.n_value, 0, n1.n_len + Libbcmath.MIN(n1.n_scale, scale))
    -          // can we return here? not in c src, but can't see why-not.
    -          // return qval;
    -        }
    -      }
    -
    -      /* Set up the divide.  Move the decimal point on n1 by n2's scale.
    -       Remember, zeros on the end of num2 are wasted effort for dividing. */
    -      scale2 = n2.n_scale // scale2 = n2->n_scale;
    -      n2ptr = n2.n_len + scale2 - 1 // n2ptr = (unsigned char *) n2.n_value+n2.n_len+scale2-1;
    -      while (scale2 > 0 && n2.n_value[n2ptr--] === 0) {
    -        scale2--
    -      }
    -
    -      len1 = n1.n_len + scale2
    -      scale1 = n1.n_scale - scale2
    -      if (scale1 < scale) {
    -        extra = scale - scale1
    -      } else {
    -        extra = 0
    -      }
    -
    -      // num1 = (unsigned char *) safe_emalloc (1, n1.n_len+n1.n_scale, extra+2):
    -      num1 = Libbcmath.safe_emalloc(1, n1.n_len + n1.n_scale, extra + 2)
    -      if (num1 === null) {
    -        Libbcmath.bc_out_of_memory()
    -      }
    -      // memset (num1, 0, n1->n_len+n1->n_scale+extra+2):
    -      Libbcmath.memset(num1, 0, 0, n1.n_len + n1.n_scale + extra + 2)
    -      // memcpy (num1+1, n1.n_value, n1.n_len+n1.n_scale):
    -      Libbcmath.memcpy(num1, 1, n1.n_value, 0, n1.n_len + n1.n_scale)
    -      // len2 = n2->n_len + scale2:
    -      len2 = n2.n_len + scale2
    -      // num2 = (unsigned char *) safe_emalloc (1, len2, 1):
    -      num2 = Libbcmath.safe_emalloc(1, len2, 1)
    -      if (num2 === null) {
    -        Libbcmath.bc_out_of_memory()
    -      }
    -      // memcpy (num2, n2.n_value, len2):
    -      Libbcmath.memcpy(num2, 0, n2.n_value, 0, len2)
    -      // *(num2+len2) = 0:
    -      num2[len2] = 0
    -      // n2ptr = num2:
    -      n2ptr = 0
    -      // while (*n2ptr === 0):
    -      while (num2[n2ptr] === 0) {
    -        n2ptr++
    -        len2--
    -      }
    -
    -      // Calculate the number of quotient digits.
    -      if (len2 > len1 + scale) {
    -        qdigits = scale + 1
    -        zero = true
    -      } else {
    -        zero = false
    -        if (len2 > len1) {
    -          qdigits = scale + 1 // One for the zero integer part.
    -        } else {
    -          qdigits = len1 - len2 + scale + 1
    -        }
    -      }
    -
    -      // Allocate and zero the storage for the quotient.
    -      // qval = bc_new_num (qdigits-scale,scale);
    -      qval = Libbcmath.bc_new_num(qdigits - scale, scale)
    -      // memset (qval->n_value, 0, qdigits);
    -      Libbcmath.memset(qval.n_value, 0, 0, qdigits)
    -      // Allocate storage for the temporary storage mval.
    -      // mval = (unsigned char *) safe_emalloc (1, len2, 1);
    -      mval = Libbcmath.safe_emalloc(1, len2, 1)
    -      if (mval === null) {
    -        Libbcmath.bc_out_of_memory()
    -      }
    -
    -      // Now for the full divide algorithm.
    -      if (!zero) {
    -        // Normalize
    -        // norm = Libbcmath.cint(10 / (Libbcmath.cint(n2.n_value[n2ptr]) + 1));
    -        // norm =  10 / ((int)*n2ptr + 1)
    -        norm = Math.floor(10 / (n2.n_value[n2ptr] + 1)) // norm =  10 / ((int)*n2ptr + 1);
    -        if (norm !== 1) {
    -          // Libbcmath._one_mult(num1, len1+scale1+extra+1, norm, num1);
    -          Libbcmath._one_mult(num1, 0, len1 + scale1 + extra + 1, norm, num1, 0)
    -          // Libbcmath._one_mult(n2ptr, len2, norm, n2ptr);
    -          Libbcmath._one_mult(n2.n_value, n2ptr, len2, norm, n2.n_value, n2ptr)
    -          // @todo: Check: Is the pointer affected by the call? if so,
    -          // maybe need to adjust points on return?
    -        }
    -
    -        // Initialize divide loop.
    -        qdig = 0
    -        if (len2 > len1) {
    -          qptr = len2 - len1 // qptr = (unsigned char *) qval.n_value+len2-len1;
    -        } else {
    -          qptr = 0 // qptr = (unsigned char *) qval.n_value;
    -        }
    -
    -        // Loop
    -        while (qdig <= len1 + scale - len2) {
    -          // Calculate the quotient digit guess.
    -          if (n2.n_value[n2ptr] === num1[qdig]) {
    -            qguess = 9
    -          } else {
    -            qguess = Math.floor((num1[qdig] * 10 + num1[qdig + 1]) / n2.n_value[n2ptr])
    -          }
    -          // Test qguess.
    -
    -          if (
    -            n2.n_value[n2ptr + 1] * qguess >
    -            (num1[qdig] * 10 + num1[qdig + 1] - n2.n_value[n2ptr] * qguess) * 10 + num1[qdig + 2]
    -          ) {
    -            qguess--
    -            // And again.
    -            if (
    -              n2.n_value[n2ptr + 1] * qguess >
    -              (num1[qdig] * 10 + num1[qdig + 1] - n2.n_value[n2ptr] * qguess) * 10 + num1[qdig + 2]
    -            ) {
    -              qguess--
    -            }
    -          }
    -
    -          // Multiply and subtract.
    -          borrow = 0
    -          if (qguess !== 0) {
    -            mval[0] = 0 //* mval = 0; // @CHECK is this to fix ptr2 < 0?
    -            // _one_mult (n2ptr, len2, qguess, mval+1); // @CHECK
    -            Libbcmath._one_mult(n2.n_value, n2ptr, len2, qguess, mval, 1)
    -            ptr1 = qdig + len2 // (unsigned char *) num1+qdig+len2;
    -            ptr2 = len2 // (unsigned char *) mval+len2;
    -            // @todo: CHECK: Does a negative pointer return null?
    -            // ptr2 can be < 0 here as ptr1 = len2, thus count < len2+1 will always fail ?
    -            for (count = 0; count < len2 + 1; count++) {
    -              if (ptr2 < 0) {
    -                // val = Libbcmath.cint(num1[ptr1]) - 0 - borrow;
    -                // val = (int) *ptr1 - (int) *ptr2-- - borrow;
    -                val = num1[ptr1] - 0 - borrow // val = (int) *ptr1 - (int) *ptr2-- - borrow;
    -              } else {
    -                // val = Libbcmath.cint(num1[ptr1]) - Libbcmath.cint(mval[ptr2--]) - borrow;
    -                // val = (int) *ptr1 - (int) *ptr2-- - borrow;
    -                // val = (int) *ptr1 - (int) *ptr2-- - borrow;
    -                val = num1[ptr1] - mval[ptr2--] - borrow
    -              }
    -              if (val < 0) {
    -                val += 10
    -                borrow = 1
    -              } else {
    -                borrow = 0
    -              }
    -              num1[ptr1--] = val
    -            }
    -          }
    -
    -          // Test for negative result.
    -          if (borrow === 1) {
    -            qguess--
    -            ptr1 = qdig + len2 // (unsigned char *) num1+qdig+len2;
    -            ptr2 = len2 - 1 // (unsigned char *) n2ptr+len2-1;
    -            carry = 0
    -            for (count = 0; count < len2; count++) {
    -              if (ptr2 < 0) {
    -                // val = Libbcmath.cint(num1[ptr1]) + 0 + carry;
    -                // val = (int) *ptr1 + (int) *ptr2-- + carry;
    -                // val = (int) *ptr1 + (int) *ptr2-- + carry;
    -                val = num1[ptr1] + 0 + carry
    -              } else {
    -                // val = Libbcmath.cint(num1[ptr1]) + Libbcmath.cint(n2.n_value[ptr2--]) + carry;
    -                // val = (int) *ptr1 + (int) *ptr2-- + carry;
    -                // val = (int) *ptr1 + (int) *ptr2-- + carry;
    -                val = num1[ptr1] + n2.n_value[ptr2--] + carry
    -              }
    -              if (val > 9) {
    -                val -= 10
    -                carry = 1
    -              } else {
    -                carry = 0
    -              }
    -              num1[ptr1--] = val //* ptr1-- = val;
    -            }
    -            if (carry === 1) {
    -              // num1[ptr1] = Libbcmath.cint((num1[ptr1] + 1) % 10);
    -              // *ptr1 = (*ptr1 + 1) % 10; // @CHECK
    -              // *ptr1 = (*ptr1 + 1) % 10; // @CHECK
    -              num1[ptr1] = (num1[ptr1] + 1) % 10
    -            }
    -          }
    -
    -          // We now know the quotient digit.
    -          qval.n_value[qptr++] = qguess //* qptr++ =  qguess;
    -          qdig++
    -        }
    -      }
    -
    -      // Clean up and return the number.
    -      qval.n_sign = n1.n_sign === n2.n_sign ? Libbcmath.PLUS : Libbcmath.MINUS
    -      if (Libbcmath.bc_is_zero(qval)) {
    -        qval.n_sign = Libbcmath.PLUS
    -      }
    -      Libbcmath._bc_rm_leading_zeros(qval)
    -
    -      return qval
    -
    -      // return 0;    // Everything is OK.
    -    },
    -
    -    MUL_BASE_DIGITS: 80,
    -    MUL_SMALL_DIGITS: 80 / 4,
    -    // #define MUL_SMALL_DIGITS mul_base_digits/4
    -
    -    /* The multiply routine.  N2 times N1 is put int PROD with the scale of
    -   the result being MIN(N2 scale+N1 scale, MAX (SCALE, N2 scale, N1 scale)).
    -   */
    -    /**
    -     * @param n1 bc_num
    -     * @param n2 bc_num
    -     * @param scale [int] optional
    -     */
    -    bc_multiply: function (n1, n2, scale) {
    -      let pval // bc_num
    -      let len1
    -      let len2 // int
    -      let fullScale
    -      let prodScale // int
    -      // Initialize things.
    -      len1 = n1.n_len + n1.n_scale
    -      len2 = n2.n_len + n2.n_scale
    -      fullScale = n1.n_scale + n2.n_scale
    -      prodScale = Libbcmath.MIN(fullScale, Libbcmath.MAX(scale, Libbcmath.MAX(n1.n_scale, n2.n_scale)))
    -
    -      // pval = Libbcmath.bc_init_num(); // allow pass by ref
    -      // Do the multiply
    -      pval = Libbcmath._bc_rec_mul(n1, len1, n2, len2, fullScale)
    -
    -      // Assign to prod and clean up the number.
    -      pval.n_sign = n1.n_sign === n2.n_sign ? Libbcmath.PLUS : Libbcmath.MINUS
    -      // pval.n_value = pval.nPtr;
    -      pval.n_len = len2 + len1 + 1 - fullScale
    -      pval.n_scale = prodScale
    -      Libbcmath._bc_rm_leading_zeros(pval)
    -      if (Libbcmath.bc_is_zero(pval)) {
    -        pval.n_sign = Libbcmath.PLUS
    -      }
    -      // bc_free_num (prod);
    -      return pval
    -    },
    -
    -    new_sub_num: function (length, scale, value, ptr = 0) {
    -      const temp = new Libbcmath.bc_num()
    -      temp.n_sign = Libbcmath.PLUS
    -      temp.n_len = length
    -      temp.n_scale = scale
    -      temp.n_value = Libbcmath.safe_emalloc(1, length + scale, 0)
    -      Libbcmath.memcpy(temp.n_value, 0, value, ptr, length + scale)
    -      return temp
    -    },
    -
    -    _bc_simp_mul: function (n1, n1len, n2, n2len, fullScale) {
    -      let prod // bc_num
    -      let n1ptr
    -      let n2ptr
    -      let pvptr // char *n1ptr, *n2ptr, *pvptr;
    -      let n1end
    -      let n2end // char *n1end, *n2end;        // To the end of n1 and n2.
    -      let indx
    -      let sum
    -      let prodlen // int indx, sum, prodlen;
    -      prodlen = n1len + n2len + 1
    -
    -      prod = Libbcmath.bc_new_num(prodlen, 0)
    -
    -      n1end = n1len - 1 // (char *) (n1->n_value + n1len - 1);
    -      n2end = n2len - 1 // (char *) (n2->n_value + n2len - 1);
    -      pvptr = prodlen - 1 // (char *) ((*prod)->n_value + prodlen - 1);
    -      sum = 0
    -
    -      // Here is the loop...
    -      for (indx = 0; indx < prodlen - 1; indx++) {
    -        // (char *) (n1end - MAX(0, indx-n2len+1));
    -        n1ptr = n1end - Libbcmath.MAX(0, indx - n2len + 1)
    -        // (char *) (n2end - MIN(indx, n2len-1));
    -        n2ptr = n2end - Libbcmath.MIN(indx, n2len - 1)
    -        while (n1ptr >= 0 && n2ptr <= n2end) {
    -          // sum += *n1ptr-- * *n2ptr++;
    -          sum += n1.n_value[n1ptr--] * n2.n_value[n2ptr++]
    -        }
    -        //* pvptr-- = sum % BASE;
    -        prod.n_value[pvptr--] = Math.floor(sum % Libbcmath.BASE)
    -        sum = Math.floor(sum / Libbcmath.BASE) // sum = sum / BASE;
    -      }
    -      prod.n_value[pvptr] = sum //* pvptr = sum;
    -      return prod
    -    },
    -
    -    /* A special adder/subtractor for the recursive divide and conquer
    -       multiply algorithm.  Note: if sub is called, accum must
    -       be larger that what is being subtracted.  Also, accum and val
    -       must have n_scale = 0.  (e.g. they must look like integers. *) */
    -    _bc_shift_addsub: function (accum, val, shift, sub) {
    -      let accp
    -      let valp // signed char *accp, *valp;
    -      let count
    -      let carry // int  count, carry;
    -      count = val.n_len
    -      if (val.n_value[0] === 0) {
    -        count--
    -      }
    -
    -      // assert (accum->n_len+accum->n_scale >= shift+count);
    -      if (accum.n_len + accum.n_scale < shift + count) {
    -        throw new Error('len + scale < shift + count') // ?? I think that's what assert does :)
    -      }
    -
    -      // Set up pointers and others
    -      // (signed char *)(accum->n_value + accum->n_len + accum->n_scale - shift - 1);
    -      accp = accum.n_len + accum.n_scale - shift - 1
    -      valp = val.n_len - 1 // (signed char *)(val->n_value + val->n_len - 1);
    -      carry = 0
    -      if (sub) {
    -        // Subtraction, carry is really borrow.
    -        while (count--) {
    -          accum.n_value[accp] -= val.n_value[valp--] + carry //* accp -= *valp-- + carry;
    -          if (accum.n_value[accp] < 0) {
    -            // if (*accp < 0)
    -            carry = 1
    -            accum.n_value[accp--] += Libbcmath.BASE //* accp-- += BASE;
    -          } else {
    -            carry = 0
    -            accp--
    -          }
    -        }
    -        while (carry) {
    -          accum.n_value[accp] -= carry //* accp -= carry;
    -          if (accum.n_value[accp] < 0) {
    -            // if (*accp < 0)
    -            accum.n_value[accp--] += Libbcmath.BASE //    *accp-- += BASE;
    -          } else {
    -            carry = 0
    -          }
    -        }
    -      } else {
    -        // Addition
    -        while (count--) {
    -          accum.n_value[accp] += val.n_value[valp--] + carry //* accp += *valp-- + carry;
    -          if (accum.n_value[accp] > Libbcmath.BASE - 1) {
    -            // if (*accp > (BASE-1))
    -            carry = 1
    -            accum.n_value[accp--] -= Libbcmath.BASE //* accp-- -= BASE;
    -          } else {
    -            carry = 0
    -            accp--
    -          }
    -        }
    -        while (carry) {
    -          accum.n_value[accp] += carry //* accp += carry;
    -          if (accum.n_value[accp] > Libbcmath.BASE - 1) {
    -            // if (*accp > (BASE-1))
    -            accum.n_value[accp--] -= Libbcmath.BASE //* accp-- -= BASE;
    -          } else {
    -            carry = 0
    -          }
    -        }
    -      }
    -      return true // accum is the pass-by-reference return
    -    },
    -
    -    /* Recursive divide and conquer multiply algorithm.
    -       based on
    -       Let u = u0 + u1*(b^n)
    -       Let v = v0 + v1*(b^n)
    -       Then uv = (B^2n+B^n)*u1*v1 + B^n*(u1-u0)*(v0-v1) + (B^n+1)*u0*v0
    -
    -       B is the base of storage, number of digits in u1,u0 close to equal.
    -    */
    -    _bc_rec_mul: function (u, ulen, v, vlen, fullScale) {
    -      let prod // @return
    -      let u0
    -      let u1
    -      let v0
    -      let v1 // bc_num
    -      // var u0len,
    -      // var v0len // int
    -      let m1
    -      let m2
    -      let m3
    -      let d1
    -      let d2 // bc_num
    -      let n
    -      let prodlen
    -      let m1zero // int
    -      let d1len
    -      let d2len // int
    -      // Base case?
    -      if (
    -        ulen + vlen < Libbcmath.MUL_BASE_DIGITS ||
    -        ulen < Libbcmath.MUL_SMALL_DIGITS ||
    -        vlen < Libbcmath.MUL_SMALL_DIGITS
    -      ) {
    -        return Libbcmath._bc_simp_mul(u, ulen, v, vlen, fullScale)
    -      }
    -
    -      // Calculate n -- the u and v split point in digits.
    -      n = Math.floor((Libbcmath.MAX(ulen, vlen) + 1) / 2)
    -
    -      // Split u and v.
    -      if (ulen < n) {
    -        u1 = Libbcmath.bc_init_num() // u1 = bc_copy_num (BCG(_zero_));
    -        u0 = Libbcmath.new_sub_num(ulen, 0, u.n_value)
    -      } else {
    -        u1 = Libbcmath.new_sub_num(ulen - n, 0, u.n_value)
    -        u0 = Libbcmath.new_sub_num(n, 0, u.n_value, ulen - n)
    -      }
    -      if (vlen < n) {
    -        v1 = Libbcmath.bc_init_num() // bc_copy_num (BCG(_zero_));
    -        v0 = Libbcmath.new_sub_num(vlen, 0, v.n_value)
    -      } else {
    -        v1 = Libbcmath.new_sub_num(vlen - n, 0, v.n_value)
    -        v0 = Libbcmath.new_sub_num(n, 0, v.n_value, vlen - n)
    -      }
    -      Libbcmath._bc_rm_leading_zeros(u1)
    -      Libbcmath._bc_rm_leading_zeros(u0)
    -      // var u0len = u0.n_len
    -      Libbcmath._bc_rm_leading_zeros(v1)
    -      Libbcmath._bc_rm_leading_zeros(v0)
    -      // var v0len = v0.n_len
    -
    -      m1zero = Libbcmath.bc_is_zero(u1) || Libbcmath.bc_is_zero(v1)
    -
    -      // Calculate sub results ...
    -      d1 = Libbcmath.bc_init_num() // needed?
    -      d2 = Libbcmath.bc_init_num() // needed?
    -      d1 = Libbcmath.bc_sub(u1, u0, 0)
    -      d1len = d1.n_len
    -
    -      d2 = Libbcmath.bc_sub(v0, v1, 0)
    -      d2len = d2.n_len
    -
    -      // Do recursive multiplies and shifted adds.
    -      if (m1zero) {
    -        m1 = Libbcmath.bc_init_num() // bc_copy_num (BCG(_zero_));
    -      } else {
    -        // m1 = Libbcmath.bc_init_num(); //allow pass-by-ref
    -        m1 = Libbcmath._bc_rec_mul(u1, u1.n_len, v1, v1.n_len, 0)
    -      }
    -      if (Libbcmath.bc_is_zero(d1) || Libbcmath.bc_is_zero(d2)) {
    -        m2 = Libbcmath.bc_init_num() // bc_copy_num (BCG(_zero_));
    -      } else {
    -        // m2 = Libbcmath.bc_init_num(); //allow pass-by-ref
    -        m2 = Libbcmath._bc_rec_mul(d1, d1len, d2, d2len, 0)
    -      }
    -
    -      if (Libbcmath.bc_is_zero(u0) || Libbcmath.bc_is_zero(v0)) {
    -        m3 = Libbcmath.bc_init_num() // bc_copy_num (BCG(_zero_));
    -      } else {
    -        // m3 = Libbcmath.bc_init_num(); //allow pass-by-ref
    -        m3 = Libbcmath._bc_rec_mul(u0, u0.n_len, v0, v0.n_len, 0)
    -      }
    -
    -      // Initialize product
    -      prodlen = ulen + vlen + 1
    -      prod = Libbcmath.bc_new_num(prodlen, 0)
    -
    -      if (!m1zero) {
    -        Libbcmath._bc_shift_addsub(prod, m1, 2 * n, 0)
    -        Libbcmath._bc_shift_addsub(prod, m1, n, 0)
    -      }
    -      Libbcmath._bc_shift_addsub(prod, m3, n, 0)
    -      Libbcmath._bc_shift_addsub(prod, m3, 0, 0)
    -      Libbcmath._bc_shift_addsub(prod, m2, n, d1.n_sign !== d2.n_sign)
    -
    -      return prod
    -      // Now clean up!
    -      // bc_free_num (&u1);
    -      // bc_free_num (&u0);
    -      // bc_free_num (&v1);
    -      // bc_free_num (&m1);
    -      // bc_free_num (&v0);
    -      // bc_free_num (&m2);
    -      // bc_free_num (&m3);
    -      // bc_free_num (&d1);
    -      // bc_free_num (&d2);
    -    },
    -
    -    /**
    -     *
    -     * @param {bc_num} n1
    -     * @param {bc_num} n2
    -     * @param {boolean} useSign
    -     * @param {boolean} ignoreLast
    -     * @return -1, 0, 1 (see bc_compare)
    -     */
    -    _bc_do_compare: function (n1, n2, useSign, ignoreLast) {
    -      let n1ptr
    -      let n2ptr // int
    -      let count // int
    -      // First, compare signs.
    -      if (useSign && n1.n_sign !== n2.n_sign) {
    -        if (n1.n_sign === Libbcmath.PLUS) {
    -          return 1 // Positive N1 > Negative N2
    -        } else {
    -          return -1 // Negative N1 < Positive N1
    -        }
    -      }
    -
    -      // Now compare the magnitude.
    -      if (n1.n_len !== n2.n_len) {
    -        if (n1.n_len > n2.n_len) {
    -          // Magnitude of n1 > n2.
    -          if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    -            return 1
    -          } else {
    -            return -1
    -          }
    -        } else {
    -          // Magnitude of n1 < n2.
    -          if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    -            return -1
    -          } else {
    -            return 1
    -          }
    -        }
    -      }
    -
    -      /* If we get here, they have the same number of integer digits.
    -     check the integer part and the equal length part of the fraction. */
    -      count = n1.n_len + Math.min(n1.n_scale, n2.n_scale)
    -      n1ptr = 0
    -      n2ptr = 0
    -
    -      while (count > 0 && n1.n_value[n1ptr] === n2.n_value[n2ptr]) {
    -        n1ptr++
    -        n2ptr++
    -        count--
    -      }
    -
    -      if (ignoreLast && count === 1 && n1.n_scale === n2.n_scale) {
    -        return 0
    -      }
    -
    -      if (count !== 0) {
    -        if (n1.n_value[n1ptr] > n2.n_value[n2ptr]) {
    -          // Magnitude of n1 > n2.
    -          if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    -            return 1
    -          } else {
    -            return -1
    -          }
    -        } else {
    -          // Magnitude of n1 < n2.
    -          if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    -            return -1
    -          } else {
    -            return 1
    -          }
    -        }
    -      }
    -
    -      // They are equal up to the last part of the equal part of the fraction.
    -      if (n1.n_scale !== n2.n_scale) {
    -        if (n1.n_scale > n2.n_scale) {
    -          for (count = n1.n_scale - n2.n_scale; count > 0; count--) {
    -            if (n1.n_value[n1ptr++] !== 0) {
    -              // Magnitude of n1 > n2.
    -              if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    -                return 1
    -              } else {
    -                return -1
    -              }
    -            }
    -          }
    -        } else {
    -          for (count = n2.n_scale - n1.n_scale; count > 0; count--) {
    -            if (n2.n_value[n2ptr++] !== 0) {
    -              // Magnitude of n1 < n2.
    -              if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    -                return -1
    -              } else {
    -                return 1
    -              }
    -            }
    -          }
    -        }
    -      }
    -
    -      // They must be equal!
    -      return 0
    -    },
    -
    -    /* Here is the full subtract routine that takes care of negative numbers.
    -   N2 is subtracted from N1 and the result placed in RESULT.  SCALE_MIN
    -   is the minimum scale for the result. */
    -    bc_sub: function (n1, n2, scaleMin) {
    -      let diff // bc_num
    -      let cmpRes
    -      let resScale // int
    -      if (n1.n_sign !== n2.n_sign) {
    -        diff = Libbcmath._bc_do_add(n1, n2, scaleMin)
    -        diff.n_sign = n1.n_sign
    -      } else {
    -        // subtraction must be done.
    -        // Compare magnitudes.
    -        cmpRes = Libbcmath._bc_do_compare(n1, n2, false, false)
    -        switch (cmpRes) {
    -          case -1:
    -            // n1 is less than n2, subtract n1 from n2.
    -            diff = Libbcmath._bc_do_sub(n2, n1, scaleMin)
    -            diff.n_sign = n2.n_sign === Libbcmath.PLUS ? Libbcmath.MINUS : Libbcmath.PLUS
    -            break
    -          case 0:
    -            // They are equal! return zero!
    -            resScale = Libbcmath.MAX(scaleMin, Libbcmath.MAX(n1.n_scale, n2.n_scale))
    -            diff = Libbcmath.bc_new_num(1, resScale)
    -            Libbcmath.memset(diff.n_value, 0, 0, resScale + 1)
    -            break
    -          case 1:
    -            // n2 is less than n1, subtract n2 from n1.
    -            diff = Libbcmath._bc_do_sub(n1, n2, scaleMin)
    -            diff.n_sign = n1.n_sign
    -            break
    -        }
    -      }
    -
    -      // Clean up and return.
    -      // bc_free_num (result);
    -      //* result = diff;
    -      return diff
    -    },
    -
    -    _bc_do_add: function (n1, n2, scaleMin) {
    -      let sum // bc_num
    -      let sumScale
    -      let sumDigits // int
    -      let n1ptr
    -      let n2ptr
    -      let sumptr // int
    -      let carry
    -      let n1bytes
    -      let n2bytes // int
    -      let tmp // int
    -
    -      // Prepare sum.
    -      sumScale = Libbcmath.MAX(n1.n_scale, n2.n_scale)
    -      sumDigits = Libbcmath.MAX(n1.n_len, n2.n_len) + 1
    -      sum = Libbcmath.bc_new_num(sumDigits, Libbcmath.MAX(sumScale, scaleMin))
    -
    -      // Start with the fraction part.  Initialize the pointers.
    -      n1bytes = n1.n_scale
    -      n2bytes = n2.n_scale
    -      n1ptr = n1.n_len + n1bytes - 1
    -      n2ptr = n2.n_len + n2bytes - 1
    -      sumptr = sumScale + sumDigits - 1
    -
    -      // Add the fraction part.  First copy the longer fraction
    -      // (ie when adding 1.2345 to 1 we know .2345 is correct already) .
    -      if (n1bytes !== n2bytes) {
    -        if (n1bytes > n2bytes) {
    -          // n1 has more dp then n2
    -          while (n1bytes > n2bytes) {
    -            sum.n_value[sumptr--] = n1.n_value[n1ptr--]
    -            // *sumptr-- = *n1ptr--;
    -            n1bytes--
    -          }
    -        } else {
    -          // n2 has more dp then n1
    -          while (n2bytes > n1bytes) {
    -            sum.n_value[sumptr--] = n2.n_value[n2ptr--]
    -            // *sumptr-- = *n2ptr--;
    -            n2bytes--
    -          }
    -        }
    -      }
    -
    -      // Now add the remaining fraction part and equal size integer parts.
    -      n1bytes += n1.n_len
    -      n2bytes += n2.n_len
    -      carry = 0
    -      while (n1bytes > 0 && n2bytes > 0) {
    -        // add the two numbers together
    -        tmp = n1.n_value[n1ptr--] + n2.n_value[n2ptr--] + carry
    -        // *sumptr = *n1ptr-- + *n2ptr-- + carry;
    -        // check if they are >= 10 (impossible to be more then 18)
    -        if (tmp >= Libbcmath.BASE) {
    -          carry = 1
    -          tmp -= Libbcmath.BASE // yep, subtract 10, add a carry
    -        } else {
    -          carry = 0
    -        }
    -        sum.n_value[sumptr] = tmp
    -        sumptr--
    -        n1bytes--
    -        n2bytes--
    -      }
    -
    -      // Now add carry the [rest of the] longer integer part.
    -      if (n1bytes === 0) {
    -        // n2 is a bigger number then n1
    -        while (n2bytes-- > 0) {
    -          tmp = n2.n_value[n2ptr--] + carry
    -          // *sumptr = *n2ptr-- + carry;
    -          if (tmp >= Libbcmath.BASE) {
    -            carry = 1
    -            tmp -= Libbcmath.BASE
    -          } else {
    -            carry = 0
    -          }
    -          sum.n_value[sumptr--] = tmp
    -        }
    -      } else {
    -        // n1 is bigger then n2..
    -        while (n1bytes-- > 0) {
    -          tmp = n1.n_value[n1ptr--] + carry
    -          // *sumptr = *n1ptr-- + carry;
    -          if (tmp >= Libbcmath.BASE) {
    -            carry = 1
    -            tmp -= Libbcmath.BASE
    -          } else {
    -            carry = 0
    -          }
    -          sum.n_value[sumptr--] = tmp
    -        }
    -      }
    -
    -      // Set final carry.
    -      if (carry === 1) {
    -        sum.n_value[sumptr] += 1
    -        // *sumptr += 1;
    -      }
    -
    -      // Adjust sum and return.
    -      Libbcmath._bc_rm_leading_zeros(sum)
    -      return sum
    -    },
    -
    -    /**
    -     * Perform a subtraction
    -     *
    -     * Perform subtraction: N2 is subtracted from N1 and the value is
    -     *  returned.  The signs of N1 and N2 are ignored.  Also, N1 is
    -     *  assumed to be larger than N2.  SCALE_MIN is the minimum scale
    -     *  of the result.
    -     *
    -     * Basic school maths says to subtract 2 numbers..
    -     * 1. make them the same length, the decimal places, and the integer part
    -     * 2. start from the right and subtract the two numbers from each other
    -     * 3. if the sum of the 2 numbers < 0, carry -1 to the next set and add 10
    -     * (ie 18 > carry 1 becomes 8). thus 0.9 + 0.9 = 1.8
    -     *
    -     * @param {bc_num} n1
    -     * @param {bc_num} n2
    -     * @param {int} scaleMin
    -     * @return bc_num
    -     */
    -    _bc_do_sub: function (n1, n2, scaleMin) {
    -      let diff // bc_num
    -      let diffScale
    -      let diffLen // int
    -      let minScale
    -      let minLen // int
    -      let n1ptr
    -      let n2ptr
    -      let diffptr // int
    -      let borrow
    -      let count
    -      let val // int
    -      // Allocate temporary storage.
    -      diffLen = Libbcmath.MAX(n1.n_len, n2.n_len)
    -      diffScale = Libbcmath.MAX(n1.n_scale, n2.n_scale)
    -      minLen = Libbcmath.MIN(n1.n_len, n2.n_len)
    -      minScale = Libbcmath.MIN(n1.n_scale, n2.n_scale)
    -      diff = Libbcmath.bc_new_num(diffLen, Libbcmath.MAX(diffScale, scaleMin))
    -
    -      /* Not needed?
    -      // Zero extra digits made by scaleMin.
    -      if (scaleMin > diffScale) {
    -        diffptr = (char *) (diff->n_value + diffLen + diffScale);
    -        for (count = scaleMin - diffScale; count > 0; count--) {
    -          *diffptr++ = 0;
    -        }
    -      }
    -      */
    -
    -      // Initialize the subtract.
    -      n1ptr = n1.n_len + n1.n_scale - 1
    -      n2ptr = n2.n_len + n2.n_scale - 1
    -      diffptr = diffLen + diffScale - 1
    -
    -      // Subtract the numbers.
    -      borrow = 0
    -
    -      // Take care of the longer scaled number.
    -      if (n1.n_scale !== minScale) {
    -        // n1 has the longer scale
    -        for (count = n1.n_scale - minScale; count > 0; count--) {
    -          diff.n_value[diffptr--] = n1.n_value[n1ptr--]
    -          // *diffptr-- = *n1ptr--;
    -        }
    -      } else {
    -        // n2 has the longer scale
    -        for (count = n2.n_scale - minScale; count > 0; count--) {
    -          val = 0 - n2.n_value[n2ptr--] - borrow
    -          // val = - *n2ptr-- - borrow;
    -          if (val < 0) {
    -            val += Libbcmath.BASE
    -            borrow = 1
    -          } else {
    -            borrow = 0
    -          }
    -          diff.n_value[diffptr--] = val
    -          //* diffptr-- = val;
    -        }
    -      }
    -
    -      // Now do the equal length scale and integer parts.
    -      for (count = 0; count < minLen + minScale; count++) {
    -        val = n1.n_value[n1ptr--] - n2.n_value[n2ptr--] - borrow
    -        // val = *n1ptr-- - *n2ptr-- - borrow;
    -        if (val < 0) {
    -          val += Libbcmath.BASE
    -          borrow = 1
    -        } else {
    -          borrow = 0
    -        }
    -        diff.n_value[diffptr--] = val
    -        //* diffptr-- = val;
    -      }
    -
    -      // If n1 has more digits then n2, we now do that subtract.
    -      if (diffLen !== minLen) {
    -        for (count = diffLen - minLen; count > 0; count--) {
    -          val = n1.n_value[n1ptr--] - borrow
    -          // val = *n1ptr-- - borrow;
    -          if (val < 0) {
    -            val += Libbcmath.BASE
    -            borrow = 1
    -          } else {
    -            borrow = 0
    -          }
    -          diff.n_value[diffptr--] = val
    -        }
    -      }
    -
    -      // Clean up and return.
    -      Libbcmath._bc_rm_leading_zeros(diff)
    -      return diff
    -    },
    -
    -    /**
    -     *
    -     * @param {int} length
    -     * @param {int} scale
    -     * @return bc_num
    -     */
    -    bc_new_num: function (length, scale) {
    -      let temp // bc_num
    -      temp = new Libbcmath.bc_num()
    -      temp.n_sign = Libbcmath.PLUS
    -      temp.n_len = length
    -      temp.n_scale = scale
    -      temp.n_value = Libbcmath.safe_emalloc(1, length + scale, 0)
    -      Libbcmath.memset(temp.n_value, 0, 0, length + scale)
    -      return temp
    -    },
    -
    -    safe_emalloc: function (size, len, extra) {
    -      return new Array(size * len + extra)
    -    },
    -
    -    /**
    -     * Create a new number
    -     */
    -    bc_init_num: function () {
    -      return new Libbcmath.bc_new_num(1, 0)
    -    },
    -
    -    _bc_rm_leading_zeros: function (num) {
    -      // We can move n_value to point to the first non zero digit!
    -      while (num.n_value[0] === 0 && num.n_len > 1) {
    -        num.n_value.shift()
    -        num.n_len--
    -      }
    -    },
    -
    -    /**
    -     * Convert to bc_num detecting scale
    -     */
    -    php_str2num: function (str) {
    -      let p
    -      p = str.indexOf('.')
    -      if (p === -1) {
    -        return Libbcmath.bc_str2num(str, 0)
    -      } else {
    -        return Libbcmath.bc_str2num(str, str.length - p)
    -      }
    -    },
    -
    -    CH_VAL: function (c) {
    -      return c - '0' // ??
    -    },
    -
    -    BCD_CHAR: function (d) {
    -      return d + '0' // ??
    -    },
    -
    -    isdigit: function (c) {
    -      return isNaN(parseInt(c, 10))
    -    },
    -
    -    bc_str2num: function (strIn, scale) {
    -      let str
    -      let num
    -      let ptr
    -      let digits
    -      let strscale
    -      let zeroInt
    -      let nptr
    -      // remove any non-expected characters
    -      // Check for valid number and count digits.
    -
    -      str = strIn.split('') // convert to array
    -      ptr = 0 // str
    -      digits = 0
    -      strscale = 0
    -      zeroInt = false
    -      if (str[ptr] === '+' || str[ptr] === '-') {
    -        ptr++ // Sign
    -      }
    -      while (str[ptr] === '0') {
    -        ptr++ // Skip leading zeros.
    -      }
    -      // while (Libbcmath.isdigit(str[ptr])) {
    -      while (str[ptr] % 1 === 0) {
    -        // Libbcmath.isdigit(str[ptr])) {
    -        ptr++
    -        digits++ // digits
    -      }
    -
    -      if (str[ptr] === '.') {
    -        ptr++ // decimal point
    -      }
    -      // while (Libbcmath.isdigit(str[ptr])) {
    -      while (str[ptr] % 1 === 0) {
    -        // Libbcmath.isdigit(str[ptr])) {
    -        ptr++
    -        strscale++ // digits
    -      }
    -
    -      if (str[ptr] || digits + strscale === 0) {
    -        // invalid number, return 0
    -        return Libbcmath.bc_init_num()
    -        //* num = bc_copy_num (BCG(_zero_));
    -      }
    -
    -      // Adjust numbers and allocate storage and initialize fields.
    -      strscale = Libbcmath.MIN(strscale, scale)
    -      if (digits === 0) {
    -        zeroInt = true
    -        digits = 1
    -      }
    -
    -      num = Libbcmath.bc_new_num(digits, strscale)
    -
    -      // Build the whole number.
    -      ptr = 0 // str
    -      if (str[ptr] === '-') {
    -        num.n_sign = Libbcmath.MINUS
    -        // (*num)->n_sign = MINUS;
    -        ptr++
    -      } else {
    -        num.n_sign = Libbcmath.PLUS
    -        // (*num)->n_sign = PLUS;
    -        if (str[ptr] === '+') {
    -          ptr++
    -        }
    -      }
    -      while (str[ptr] === '0') {
    -        ptr++ // Skip leading zeros.
    -      }
    -
    -      nptr = 0 // (*num)->n_value;
    -      if (zeroInt) {
    -        num.n_value[nptr++] = 0
    -        digits = 0
    -      }
    -      for (; digits > 0; digits--) {
    -        num.n_value[nptr++] = Libbcmath.CH_VAL(str[ptr++])
    -        //* nptr++ = CH_VAL(*ptr++);
    -      }
    -
    -      // Build the fractional part.
    -      if (strscale > 0) {
    -        ptr++ // skip the decimal point!
    -        for (; strscale > 0; strscale--) {
    -          num.n_value[nptr++] = Libbcmath.CH_VAL(str[ptr++])
    -        }
    -      }
    -
    -      return num
    -    },
    -
    -    cint: function (v) {
    -      if (typeof v === 'undefined') {
    -        v = 0
    -      }
    -      let x = parseInt(v, 10)
    -      if (isNaN(x)) {
    -        x = 0
    -      }
    -      return x
    -    },
    -
    -    /**
    -     * Basic min function
    -     * @param {int} a
    -     * @param {int} b
    -     */
    -    MIN: function (a, b) {
    -      return a > b ? b : a
    -    },
    -
    -    /**
    -     * Basic max function
    -     * @param {int} a
    -     * @param {int} b
    -     */
    -    MAX: function (a, b) {
    -      return a > b ? a : b
    -    },
    -
    -    /**
    -     * Basic odd function
    -     * @param {int} a
    -     */
    -    ODD: function (a) {
    -      return a & 1
    -    },
    -
    -    /**
    -     * replicate c function
    -     * @param {array} r     return (by reference)
    -     * @param {int} ptr
    -     * @param {string} chr    char to fill
    -     * @param {int} len       length to fill
    -     */
    -    memset: function (r, ptr, chr, len) {
    -      let i
    -      for (i = 0; i < len; i++) {
    -        r[ptr + i] = chr
    -      }
    -    },
    -
    -    /**
    -     * Replacement c function
    -     * Obviously can't work like c does, so we've added an "offset"
    -     * param so you could do memcpy(dest+1, src, len) as memcpy(dest, 1, src, len)
    -     * Also only works on arrays
    -     */
    -    memcpy: function (dest, ptr, src, srcptr, len) {
    -      let i
    -      for (i = 0; i < len; i++) {
    -        dest[ptr + i] = src[srcptr + i]
    -      }
    -      return true
    -    },
    -
    -    /**
    -     * Determine if the number specified is zero or not
    -     * @param {bc_num} num    number to check
    -     * @return boolean      true when zero, false when not zero.
    -     */
    -    bc_is_zero: function (num) {
    -      let count // int
    -      let nptr // int
    -      // Quick check.
    -      // if (num === BCG(_zero_)) return TRUE;
    -      // Initialize
    -      count = num.n_len + num.n_scale
    -      nptr = 0 // num->n_value;
    -      // The check
    -      while (count > 0 && num.n_value[nptr++] === 0) {
    -        count--
    -      }
    -
    -      if (count !== 0) {
    -        return false
    -      } else {
    -        return true
    -      }
    -    },
    -
    -    bc_out_of_memory: function () {
    -      throw new Error('(BC) Out of memory')
    -    },
    -  }
    -  return Libbcmath
    -}
    
  • src/php/_helpers/_bc.ts+1344 0 added
    @@ -0,0 +1,1344 @@
    +import type { PhpInput } from './_phpTypes.ts'
    +// SPDX-License-Identifier: LGPL-2.1-or-later
    +// NOTE: This file is LGPL licensed, not MIT like the rest of locutus.
    +// See LICENSE file for details.
    +
    +
    +function createBcLibrary() {
    +  // original by: lmeyrick (https://sourceforge.net/projects/bcmath-js/)
    +  // improved by: Brett Zamir (https://brett-zamir.me)
    +
    +  /**
    +   * BC Math Library for Javascript (LGPL-2.1)
    +   * Ported from the PHP5 bcmath extension source code,
    +   * which uses the Libbcmath package...
    +   *    Copyright (C) 1991, 1992, 1993, 1994, 1997 Free Software Foundation, Inc.
    +   *    Copyright (C) 2000 Philip A. Nelson
    +   *     The Free Software Foundation, Inc.
    +   *     59 Temple Place, Suite 330
    +   *     Boston, MA 02111-1307 USA.
    +   *      e-mail:  philnelson@acm.org
    +   *     us-mail:  Philip A. Nelson
    +   *               Computer Science Department, 9062
    +   *               Western Washington University
    +   *               Bellingham, WA 98226-9062
    +   *
    +   * bcmath-js homepage: https://sourceforge.net/projects/bcmath-js/
    +   *
    +   * This code is covered under the LGPL-2.1 license.
    +   */
    +
    +  /**
    +   * Binary Calculator (BC) Arbitrary Precision Mathematics Lib v0.10  (LGPL)
    +   * Copy of Libbcmath included in PHP5 src
    +   *
    +   * Note: this is just the shared library file and does not include the php-style functions.
    +   *       use bcmath{-min}.js for functions like bcadd, bcsub etc.
    +   *
    +   * Feel free to use how-ever you want, just email any bug-fixes/improvements
    +   * to the sourceforge project:
    +   *
    +   *
    +   * Ported from the PHP5 bcmath extension source code,
    +   * which uses the Libbcmath package...
    +   *    Copyright (C) 1991, 1992, 1993, 1994, 1997 Free Software Foundation, Inc.
    +   *    Copyright (C) 2000 Philip A. Nelson
    +   *     The Free Software Foundation, Inc.
    +   *     59 Temple Place, Suite 330
    +   *     Boston, MA 02111-1307 USA.
    +   *      e-mail:  philnelson@acm.org
    +   *     us-mail:  Philip A. Nelson
    +   *               Computer Science Department, 9062
    +   *               Western Washington University
    +   *               Bellingham, WA 98226-9062
    +   */
    +
    +  type BcNum = {
    +    n_sign: string
    +    n_len: number
    +    n_scale: number
    +    n_value: number[]
    +    toString: () => string
    +  }
    +  const digitAt = (value: number[], index: number): number => value[index] ?? 0
    +
    +  const Libbcmath = {
    +    PLUS: '+',
    +    MINUS: '-',
    +    BASE: 10,
    +    // must be 10 (for now)
    +    scale: 0,
    +    // default scale
    +    /**
    +     * Basic number structure
    +     */
    +    bc_num: function (): BcNum {
    +      const value: BcNum = {
    +        n_sign: Libbcmath.PLUS, // sign
    +        n_len: 0, // (int) The number of digits before the decimal point.
    +        n_scale: 0, // (int) The number of digits after the decimal point.
    +        // this.n_refs = null; // (int) The number of pointers to this number.
    +        // this.n_text = null; // ?? Linked list for available list.
    +        n_value: [], // array as value, where 1.23 = [1,2,3]
    +        toString: function () {
    +          let r
    +          let tmp
    +          tmp = value.n_value.join('')
    +
    +          // add minus sign (if applicable) then add the integer part
    +          r = (value.n_sign === Libbcmath.PLUS ? '' : value.n_sign) + tmp.substr(0, value.n_len)
    +
    +          // if decimal places, add a . and the decimal part
    +          if (value.n_scale > 0) {
    +            r += '.' + tmp.substr(value.n_len, value.n_scale)
    +          }
    +          return r
    +        },
    +      }
    +      return value
    +    },
    +
    +    /**
    +     * Base add function
    +     *
    +     //  Here is the full add routine that takes care of negative numbers.
    +     //  N1 is added to N2 and the result placed into RESULT.  SCALE_MIN
    +     //  is the minimum scale for the result.
    +     *
    +     * @param {bc_num} n1
    +     * @param {bc_num} n2
    +     * @param {int} scaleMin
    +     * @return bc_num
    +     */
    +    bc_add: function (n1: BcNum, n2: BcNum, scaleMin: number): BcNum {
    +      let sum: BcNum = Libbcmath.bc_init_num()
    +      let cmpRes
    +      let resScale
    +
    +      if (n1.n_sign === n2.n_sign) {
    +        sum = Libbcmath._bc_do_add(n1, n2, scaleMin)
    +        sum.n_sign = n1.n_sign
    +      } else {
    +        // subtraction must be done.
    +        cmpRes = Libbcmath._bc_do_compare(n1, n2, false, false) // Compare magnitudes.
    +        switch (cmpRes) {
    +          case -1:
    +            // n1 is less than n2, subtract n1 from n2.
    +            sum = Libbcmath._bc_do_sub(n2, n1, scaleMin)
    +            sum.n_sign = n2.n_sign
    +            break
    +
    +          case 0:
    +            // They are equal! return zero with the correct scale!
    +            resScale = Libbcmath.MAX(scaleMin, Libbcmath.MAX(n1.n_scale, n2.n_scale))
    +            sum = Libbcmath.bc_new_num(1, resScale)
    +            Libbcmath.memset(sum.n_value, 0, 0, resScale + 1)
    +            break
    +
    +          case 1:
    +            // n2 is less than n1, subtract n2 from n1.
    +            sum = Libbcmath._bc_do_sub(n1, n2, scaleMin)
    +            sum.n_sign = n1.n_sign
    +        }
    +      }
    +      return sum
    +    },
    +
    +    /**
    +     * This is the "user callable" routine to compare numbers N1 and N2.
    +     * @param {bc_num} n1
    +     * @param {bc_num} n2
    +     * @return int -1, 0, 1  (n1 < n2, ===, n1 > n2)
    +     */
    +    bc_compare: function (n1: BcNum, n2: BcNum): -1 | 0 | 1 {
    +      return Libbcmath._bc_do_compare(n1, n2, true, false)
    +    },
    +
    +    _one_mult: function (num: number[], nPtr: number, size: number, digit: number, result: number[], rPtr: number) {
    +      let carry
    +      let value // int
    +      let nptr
    +      let rptr // int pointers
    +      if (digit === 0) {
    +        Libbcmath.memset(result, 0, 0, size) // memset (result, 0, size);
    +      } else {
    +        if (digit === 1) {
    +          Libbcmath.memcpy(result, rPtr, num, nPtr, size) // memcpy (result, num, size);
    +        } else {
    +          // Initialize
    +          nptr = nPtr + size - 1 // nptr = (unsigned char *) (num+size-1);
    +          rptr = rPtr + size - 1 // rptr = (unsigned char *) (result+size-1);
    +          carry = 0
    +
    +          while (size-- > 0) {
    +            value = digitAt(num, nptr--) * digit + carry // value = *nptr-- * digit + carry;
    +            result[rptr--] = value % Libbcmath.BASE // @CHECK cint //*rptr-- = value % BASE;
    +            carry = Math.floor(value / Libbcmath.BASE) // @CHECK cint //carry = value / BASE;
    +          }
    +
    +          if (carry !== 0) {
    +            result[rptr] = carry
    +          }
    +        }
    +      }
    +    },
    +
    +    bc_divide: function (n1: BcNum, n2: BcNum, scale: number): BcNum | -1 {
    +      // var quot // bc_num return
    +      let qval // bc_num
    +      let num1: number[]
    +      let num2: number[] // string
    +      let ptr1
    +      let ptr2
    +      let n2ptr
    +      let qptr // int pointers
    +      let scale1
    +      let val // int
    +      let len1
    +      let len2
    +      let scale2
    +      let qdigits
    +      let extra
    +      let count // int
    +      let qdig
    +      let qguess
    +      let borrow
    +      let carry // int
    +      let mval: number[] // string
    +      let zero // char
    +      let norm // int
    +      // var ptrs // return object from one_mul
    +      // Test for divide by zero. (return failure)
    +      if (Libbcmath.bc_is_zero(n2)) {
    +        return -1
    +      }
    +
    +      // Test for zero divide by anything (return zero)
    +      if (Libbcmath.bc_is_zero(n1)) {
    +        return Libbcmath.bc_new_num(1, scale)
    +      }
    +
    +      /* Test for n1 equals n2 (return 1 as n1 nor n2 are zero)
    +        if (Libbcmath.bc_compare(n1, n2, Libbcmath.MAX(n1.n_scale, n2.n_scale)) === 0) {
    +          quot=Libbcmath.bc_new_num(1, scale);
    +          quot.n_value[0] = 1;
    +          return quot;
    +        }
    +      */
    +
    +      // Test for divide by 1.  If it is we must truncate.
    +      // @todo: check where scale > 0 too.. can't see why not
    +      // (ie bc_is_zero - add bc_is_one function)
    +      if (n2.n_scale === 0) {
    +        if (n2.n_len === 1 && n2.n_value[0] === 1) {
    +          qval = Libbcmath.bc_new_num(n1.n_len, scale) // qval = bc_new_num (n1->n_len, scale);
    +          qval.n_sign = n1.n_sign === n2.n_sign ? Libbcmath.PLUS : Libbcmath.MINUS
    +          // memset (&qval->n_value[n1->n_len],0,scale):
    +          Libbcmath.memset(qval.n_value, n1.n_len, 0, scale)
    +          // memcpy (qval->n_value, n1->n_value, n1->n_len + MIN(n1->n_scale,scale)):
    +          Libbcmath.memcpy(qval.n_value, 0, n1.n_value, 0, n1.n_len + Libbcmath.MIN(n1.n_scale, scale))
    +          // can we return here? not in c src, but can't see why-not.
    +          // return qval;
    +        }
    +      }
    +
    +      /* Set up the divide.  Move the decimal point on n1 by n2's scale.
    +       Remember, zeros on the end of num2 are wasted effort for dividing. */
    +      scale2 = n2.n_scale // scale2 = n2->n_scale;
    +      n2ptr = n2.n_len + scale2 - 1 // n2ptr = (unsigned char *) n2.n_value+n2.n_len+scale2-1;
    +      while (scale2 > 0 && n2.n_value[n2ptr--] === 0) {
    +        scale2--
    +      }
    +
    +      len1 = n1.n_len + scale2
    +      scale1 = n1.n_scale - scale2
    +      if (scale1 < scale) {
    +        extra = scale - scale1
    +      } else {
    +        extra = 0
    +      }
    +
    +      // num1 = (unsigned char *) safe_emalloc (1, n1.n_len+n1.n_scale, extra+2):
    +      num1 = Libbcmath.safe_emalloc(1, n1.n_len + n1.n_scale, extra + 2)
    +      if (num1 === null) {
    +        Libbcmath.bc_out_of_memory()
    +      }
    +      // memset (num1, 0, n1->n_len+n1->n_scale+extra+2):
    +      Libbcmath.memset(num1, 0, 0, n1.n_len + n1.n_scale + extra + 2)
    +      // memcpy (num1+1, n1.n_value, n1.n_len+n1.n_scale):
    +      Libbcmath.memcpy(num1, 1, n1.n_value, 0, n1.n_len + n1.n_scale)
    +      // len2 = n2->n_len + scale2:
    +      len2 = n2.n_len + scale2
    +      // num2 = (unsigned char *) safe_emalloc (1, len2, 1):
    +      num2 = Libbcmath.safe_emalloc(1, len2, 1)
    +      if (num2 === null) {
    +        Libbcmath.bc_out_of_memory()
    +      }
    +      // memcpy (num2, n2.n_value, len2):
    +      Libbcmath.memcpy(num2, 0, n2.n_value, 0, len2)
    +      // *(num2+len2) = 0:
    +      num2[len2] = 0
    +      // n2ptr = num2:
    +      n2ptr = 0
    +      // while (*n2ptr === 0):
    +      while (num2[n2ptr] === 0) {
    +        n2ptr++
    +        len2--
    +      }
    +
    +      // Calculate the number of quotient digits.
    +      if (len2 > len1 + scale) {
    +        qdigits = scale + 1
    +        zero = true
    +      } else {
    +        zero = false
    +        if (len2 > len1) {
    +          qdigits = scale + 1 // One for the zero integer part.
    +        } else {
    +          qdigits = len1 - len2 + scale + 1
    +        }
    +      }
    +
    +      // Allocate and zero the storage for the quotient.
    +      // qval = bc_new_num (qdigits-scale,scale);
    +      qval = Libbcmath.bc_new_num(qdigits - scale, scale)
    +      // memset (qval->n_value, 0, qdigits);
    +      Libbcmath.memset(qval.n_value, 0, 0, qdigits)
    +      // Allocate storage for the temporary storage mval.
    +      // mval = (unsigned char *) safe_emalloc (1, len2, 1);
    +      mval = Libbcmath.safe_emalloc(1, len2, 1)
    +      if (mval === null) {
    +        Libbcmath.bc_out_of_memory()
    +      }
    +
    +      // Now for the full divide algorithm.
    +      if (!zero) {
    +        // Normalize
    +        // norm = Libbcmath.cint(10 / (Libbcmath.cint(n2.n_value[n2ptr]) + 1));
    +        // norm =  10 / ((int)*n2ptr + 1)
    +        norm = Math.floor(10 / (digitAt(n2.n_value, n2ptr) + 1)) // norm =  10 / ((int)*n2ptr + 1);
    +        if (norm !== 1) {
    +          // Libbcmath._one_mult(num1, len1+scale1+extra+1, norm, num1);
    +          Libbcmath._one_mult(num1, 0, len1 + scale1 + extra + 1, norm, num1, 0)
    +          // Libbcmath._one_mult(n2ptr, len2, norm, n2ptr);
    +          Libbcmath._one_mult(n2.n_value, n2ptr, len2, norm, n2.n_value, n2ptr)
    +          // @todo: Check: Is the pointer affected by the call? if so,
    +          // maybe need to adjust points on return?
    +        }
    +
    +        // Initialize divide loop.
    +        qdig = 0
    +        if (len2 > len1) {
    +          qptr = len2 - len1 // qptr = (unsigned char *) qval.n_value+len2-len1;
    +        } else {
    +          qptr = 0 // qptr = (unsigned char *) qval.n_value;
    +        }
    +
    +        // Loop
    +        while (qdig <= len1 + scale - len2) {
    +          // Calculate the quotient digit guess.
    +          if (n2.n_value[n2ptr] === num1[qdig]) {
    +            qguess = 9
    +          } else {
    +            qguess = Math.floor((digitAt(num1, qdig) * 10 + digitAt(num1, qdig + 1)) / digitAt(n2.n_value, n2ptr))
    +          }
    +          // Test qguess.
    +
    +          if (
    +            digitAt(n2.n_value, n2ptr + 1) * qguess >
    +            (digitAt(num1, qdig) * 10 + digitAt(num1, qdig + 1) - digitAt(n2.n_value, n2ptr) * qguess) * 10 +
    +              digitAt(num1, qdig + 2)
    +          ) {
    +            qguess--
    +            // And again.
    +            if (
    +              digitAt(n2.n_value, n2ptr + 1) * qguess >
    +              (digitAt(num1, qdig) * 10 + digitAt(num1, qdig + 1) - digitAt(n2.n_value, n2ptr) * qguess) * 10 +
    +                digitAt(num1, qdig + 2)
    +            ) {
    +              qguess--
    +            }
    +          }
    +
    +          // Multiply and subtract.
    +          borrow = 0
    +          if (qguess !== 0) {
    +            mval[0] = 0 //* mval = 0; // @CHECK is this to fix ptr2 < 0?
    +            // _one_mult (n2ptr, len2, qguess, mval+1); // @CHECK
    +            Libbcmath._one_mult(n2.n_value, n2ptr, len2, qguess, mval, 1)
    +            ptr1 = qdig + len2 // (unsigned char *) num1+qdig+len2;
    +            ptr2 = len2 // (unsigned char *) mval+len2;
    +            // @todo: CHECK: Does a negative pointer return null?
    +            // ptr2 can be < 0 here as ptr1 = len2, thus count < len2+1 will always fail ?
    +            for (count = 0; count < len2 + 1; count++) {
    +              if (ptr2 < 0) {
    +                // val = Libbcmath.cint(num1[ptr1]) - 0 - borrow;
    +                // val = (int) *ptr1 - (int) *ptr2-- - borrow;
    +                val = digitAt(num1, ptr1) - 0 - borrow // val = (int) *ptr1 - (int) *ptr2-- - borrow;
    +              } else {
    +                // val = Libbcmath.cint(num1[ptr1]) - Libbcmath.cint(mval[ptr2--]) - borrow;
    +                // val = (int) *ptr1 - (int) *ptr2-- - borrow;
    +                // val = (int) *ptr1 - (int) *ptr2-- - borrow;
    +                val = digitAt(num1, ptr1) - digitAt(mval, ptr2--) - borrow
    +              }
    +              if (val < 0) {
    +                val += 10
    +                borrow = 1
    +              } else {
    +                borrow = 0
    +              }
    +              num1[ptr1--] = val
    +            }
    +          }
    +
    +          // Test for negative result.
    +          if (borrow === 1) {
    +            qguess--
    +            ptr1 = qdig + len2 // (unsigned char *) num1+qdig+len2;
    +            ptr2 = len2 - 1 // (unsigned char *) n2ptr+len2-1;
    +            carry = 0
    +            for (count = 0; count < len2; count++) {
    +              if (ptr2 < 0) {
    +                // val = Libbcmath.cint(num1[ptr1]) + 0 + carry;
    +                // val = (int) *ptr1 + (int) *ptr2-- + carry;
    +                // val = (int) *ptr1 + (int) *ptr2-- + carry;
    +                val = digitAt(num1, ptr1) + 0 + carry
    +              } else {
    +                // val = Libbcmath.cint(num1[ptr1]) + Libbcmath.cint(n2.n_value[ptr2--]) + carry;
    +                // val = (int) *ptr1 + (int) *ptr2-- + carry;
    +                // val = (int) *ptr1 + (int) *ptr2-- + carry;
    +                val = digitAt(num1, ptr1) + digitAt(n2.n_value, ptr2--) + carry
    +              }
    +              if (val > 9) {
    +                val -= 10
    +                carry = 1
    +              } else {
    +                carry = 0
    +              }
    +              num1[ptr1--] = val //* ptr1-- = val;
    +            }
    +            if (carry === 1) {
    +              // num1[ptr1] = Libbcmath.cint((num1[ptr1] + 1) % 10);
    +              // *ptr1 = (*ptr1 + 1) % 10; // @CHECK
    +              // *ptr1 = (*ptr1 + 1) % 10; // @CHECK
    +              num1[ptr1] = (digitAt(num1, ptr1) + 1) % 10
    +            }
    +          }
    +
    +          // We now know the quotient digit.
    +          qval.n_value[qptr++] = qguess //* qptr++ =  qguess;
    +          qdig++
    +        }
    +      }
    +
    +      // Clean up and return the number.
    +      qval.n_sign = n1.n_sign === n2.n_sign ? Libbcmath.PLUS : Libbcmath.MINUS
    +      if (Libbcmath.bc_is_zero(qval)) {
    +        qval.n_sign = Libbcmath.PLUS
    +      }
    +      Libbcmath._bc_rm_leading_zeros(qval)
    +
    +      return qval
    +
    +      // return 0;    // Everything is OK.
    +    },
    +
    +    MUL_BASE_DIGITS: 80,
    +    MUL_SMALL_DIGITS: 80 / 4,
    +    // #define MUL_SMALL_DIGITS mul_base_digits/4
    +
    +    /* The multiply routine.  N2 times N1 is put int PROD with the scale of
    +   the result being MIN(N2 scale+N1 scale, MAX (SCALE, N2 scale, N1 scale)).
    +   */
    +    /**
    +     * @param n1 bc_num
    +     * @param n2 bc_num
    +     * @param scale [int] optional
    +     */
    +    bc_multiply: function (n1: BcNum, n2: BcNum, scale: number): BcNum {
    +      let pval: BcNum // bc_num
    +      let len1
    +      let len2 // int
    +      let fullScale
    +      let prodScale // int
    +      // Initialize things.
    +      len1 = n1.n_len + n1.n_scale
    +      len2 = n2.n_len + n2.n_scale
    +      fullScale = n1.n_scale + n2.n_scale
    +      prodScale = Libbcmath.MIN(fullScale, Libbcmath.MAX(scale, Libbcmath.MAX(n1.n_scale, n2.n_scale)))
    +
    +      // pval = Libbcmath.bc_init_num(); // allow pass by ref
    +      // Do the multiply
    +      pval = Libbcmath._bc_rec_mul(n1, len1, n2, len2, fullScale)
    +
    +      // Assign to prod and clean up the number.
    +      pval.n_sign = n1.n_sign === n2.n_sign ? Libbcmath.PLUS : Libbcmath.MINUS
    +      // pval.n_value = pval.nPtr;
    +      pval.n_len = len2 + len1 + 1 - fullScale
    +      pval.n_scale = prodScale
    +      Libbcmath._bc_rm_leading_zeros(pval)
    +      if (Libbcmath.bc_is_zero(pval)) {
    +        pval.n_sign = Libbcmath.PLUS
    +      }
    +      // bc_free_num (prod);
    +      return pval
    +    },
    +
    +    new_sub_num: function (length: number, scale: number, value: number[], ptr = 0): BcNum {
    +      const temp = Libbcmath.bc_num()
    +      temp.n_sign = Libbcmath.PLUS
    +      temp.n_len = length
    +      temp.n_scale = scale
    +      temp.n_value = Libbcmath.safe_emalloc(1, length + scale, 0)
    +      Libbcmath.memcpy(temp.n_value, 0, value, ptr, length + scale)
    +      return temp
    +    },
    +
    +    _bc_simp_mul: function (n1: BcNum, n1len: number, n2: BcNum, n2len: number, fullScale: number): BcNum {
    +      let prod: BcNum // bc_num
    +      let n1ptr
    +      let n2ptr
    +      let pvptr // char *n1ptr, *n2ptr, *pvptr;
    +      let n1end
    +      let n2end // char *n1end, *n2end;        // To the end of n1 and n2.
    +      let indx
    +      let sum
    +      let prodlen // int indx, sum, prodlen;
    +      prodlen = n1len + n2len + 1
    +
    +      prod = Libbcmath.bc_new_num(prodlen, 0)
    +
    +      n1end = n1len - 1 // (char *) (n1->n_value + n1len - 1);
    +      n2end = n2len - 1 // (char *) (n2->n_value + n2len - 1);
    +      pvptr = prodlen - 1 // (char *) ((*prod)->n_value + prodlen - 1);
    +      sum = 0
    +
    +      // Here is the loop...
    +      for (indx = 0; indx < prodlen - 1; indx++) {
    +        // (char *) (n1end - MAX(0, indx-n2len+1));
    +        n1ptr = n1end - Libbcmath.MAX(0, indx - n2len + 1)
    +        // (char *) (n2end - MIN(indx, n2len-1));
    +        n2ptr = n2end - Libbcmath.MIN(indx, n2len - 1)
    +        while (n1ptr >= 0 && n2ptr <= n2end) {
    +          // sum += *n1ptr-- * *n2ptr++;
    +          sum += digitAt(n1.n_value, n1ptr--) * digitAt(n2.n_value, n2ptr++)
    +        }
    +        //* pvptr-- = sum % BASE;
    +        prod.n_value[pvptr--] = Math.floor(sum % Libbcmath.BASE)
    +        sum = Math.floor(sum / Libbcmath.BASE) // sum = sum / BASE;
    +      }
    +      prod.n_value[pvptr] = sum //* pvptr = sum;
    +      return prod
    +    },
    +
    +    /* A special adder/subtractor for the recursive divide and conquer
    +       multiply algorithm.  Note: if sub is called, accum must
    +       be larger that what is being subtracted.  Also, accum and val
    +       must have n_scale = 0.  (e.g. they must look like integers. *) */
    +    _bc_shift_addsub: function (accum: BcNum, val: BcNum, shift: number, sub: number | boolean) {
    +      let accp
    +      let valp // signed char *accp, *valp;
    +      let count
    +      let carry // int  count, carry;
    +      count = val.n_len
    +      if (val.n_value[0] === 0) {
    +        count--
    +      }
    +
    +      // assert (accum->n_len+accum->n_scale >= shift+count);
    +      if (accum.n_len + accum.n_scale < shift + count) {
    +        throw new Error('len + scale < shift + count') // ?? I think that's what assert does :)
    +      }
    +
    +      // Set up pointers and others
    +      // (signed char *)(accum->n_value + accum->n_len + accum->n_scale - shift - 1);
    +      accp = accum.n_len + accum.n_scale - shift - 1
    +      valp = val.n_len - 1 // (signed char *)(val->n_value + val->n_len - 1);
    +      carry = 0
    +      if (sub) {
    +        // Subtraction, carry is really borrow.
    +        while (count--) {
    +          accum.n_value[accp] = digitAt(accum.n_value, accp) - digitAt(val.n_value, valp--) - carry //* accp -= *valp-- + carry;
    +          if (digitAt(accum.n_value, accp) < 0) {
    +            // if (*accp < 0)
    +            carry = 1
    +            accum.n_value[accp] = digitAt(accum.n_value, accp) + Libbcmath.BASE //* accp += BASE;
    +            accp--
    +          } else {
    +            carry = 0
    +            accp--
    +          }
    +        }
    +        while (carry) {
    +          accum.n_value[accp] = digitAt(accum.n_value, accp) - carry //* accp -= carry;
    +          if (digitAt(accum.n_value, accp) < 0) {
    +            // if (*accp < 0)
    +            accum.n_value[accp] = digitAt(accum.n_value, accp) + Libbcmath.BASE //    *accp += BASE;
    +            accp--
    +          } else {
    +            carry = 0
    +          }
    +        }
    +      } else {
    +        // Addition
    +        while (count--) {
    +          accum.n_value[accp] = digitAt(accum.n_value, accp) + digitAt(val.n_value, valp--) + carry //* accp += *valp-- + carry;
    +          if (digitAt(accum.n_value, accp) > Libbcmath.BASE - 1) {
    +            // if (*accp > (BASE-1))
    +            carry = 1
    +            accum.n_value[accp] = digitAt(accum.n_value, accp) - Libbcmath.BASE //* accp -= BASE;
    +            accp--
    +          } else {
    +            carry = 0
    +            accp--
    +          }
    +        }
    +        while (carry) {
    +          accum.n_value[accp] = digitAt(accum.n_value, accp) + carry //* accp += carry;
    +          if (digitAt(accum.n_value, accp) > Libbcmath.BASE - 1) {
    +            // if (*accp > (BASE-1))
    +            accum.n_value[accp] = digitAt(accum.n_value, accp) - Libbcmath.BASE //* accp -= BASE;
    +            accp--
    +          } else {
    +            carry = 0
    +          }
    +        }
    +      }
    +      return true // accum is the pass-by-reference return
    +    },
    +
    +    /* Recursive divide and conquer multiply algorithm.
    +       based on
    +       Let u = u0 + u1*(b^n)
    +       Let v = v0 + v1*(b^n)
    +       Then uv = (B^2n+B^n)*u1*v1 + B^n*(u1-u0)*(v0-v1) + (B^n+1)*u0*v0
    +
    +       B is the base of storage, number of digits in u1,u0 close to equal.
    +    */
    +    _bc_rec_mul: function (u: BcNum, ulen: number, v: BcNum, vlen: number, fullScale: number): BcNum {
    +      let prod: BcNum // @return
    +      let u0: BcNum
    +      let u1: BcNum
    +      let v0: BcNum
    +      let v1: BcNum // bc_num
    +      // var u0len,
    +      // var v0len // int
    +      let m1: BcNum = Libbcmath.bc_init_num()
    +      let m2: BcNum = Libbcmath.bc_init_num()
    +      let m3: BcNum = Libbcmath.bc_init_num()
    +      let d1: BcNum = Libbcmath.bc_init_num()
    +      let d2: BcNum = Libbcmath.bc_init_num() // bc_num
    +      let n
    +      let prodlen
    +      let m1zero // int
    +      let d1len
    +      let d2len // int
    +      // Base case?
    +      if (
    +        ulen + vlen < Libbcmath.MUL_BASE_DIGITS ||
    +        ulen < Libbcmath.MUL_SMALL_DIGITS ||
    +        vlen < Libbcmath.MUL_SMALL_DIGITS
    +      ) {
    +        return Libbcmath._bc_simp_mul(u, ulen, v, vlen, fullScale)
    +      }
    +
    +      // Calculate n -- the u and v split point in digits.
    +      n = Math.floor((Libbcmath.MAX(ulen, vlen) + 1) / 2)
    +
    +      // Split u and v.
    +      if (ulen < n) {
    +        u1 = Libbcmath.bc_init_num() // u1 = bc_copy_num (BCG(_zero_));
    +        u0 = Libbcmath.new_sub_num(ulen, 0, u.n_value)
    +      } else {
    +        u1 = Libbcmath.new_sub_num(ulen - n, 0, u.n_value)
    +        u0 = Libbcmath.new_sub_num(n, 0, u.n_value, ulen - n)
    +      }
    +      if (vlen < n) {
    +        v1 = Libbcmath.bc_init_num() // bc_copy_num (BCG(_zero_));
    +        v0 = Libbcmath.new_sub_num(vlen, 0, v.n_value)
    +      } else {
    +        v1 = Libbcmath.new_sub_num(vlen - n, 0, v.n_value)
    +        v0 = Libbcmath.new_sub_num(n, 0, v.n_value, vlen - n)
    +      }
    +      Libbcmath._bc_rm_leading_zeros(u1)
    +      Libbcmath._bc_rm_leading_zeros(u0)
    +      // var u0len = u0.n_len
    +      Libbcmath._bc_rm_leading_zeros(v1)
    +      Libbcmath._bc_rm_leading_zeros(v0)
    +      // var v0len = v0.n_len
    +
    +      m1zero = Libbcmath.bc_is_zero(u1) || Libbcmath.bc_is_zero(v1)
    +
    +      // Calculate sub results ...
    +      d1 = Libbcmath.bc_init_num() // needed?
    +      d2 = Libbcmath.bc_init_num() // needed?
    +      d1 = Libbcmath.bc_sub(u1, u0, 0)
    +      d1len = d1.n_len
    +
    +      d2 = Libbcmath.bc_sub(v0, v1, 0)
    +      d2len = d2.n_len
    +
    +      // Do recursive multiplies and shifted adds.
    +      if (m1zero) {
    +        m1 = Libbcmath.bc_init_num() // bc_copy_num (BCG(_zero_));
    +      } else {
    +        // m1 = Libbcmath.bc_init_num(); //allow pass-by-ref
    +        m1 = Libbcmath._bc_rec_mul(u1, u1.n_len, v1, v1.n_len, 0)
    +      }
    +      if (Libbcmath.bc_is_zero(d1) || Libbcmath.bc_is_zero(d2)) {
    +        m2 = Libbcmath.bc_init_num() // bc_copy_num (BCG(_zero_));
    +      } else {
    +        // m2 = Libbcmath.bc_init_num(); //allow pass-by-ref
    +        m2 = Libbcmath._bc_rec_mul(d1, d1len, d2, d2len, 0)
    +      }
    +
    +      if (Libbcmath.bc_is_zero(u0) || Libbcmath.bc_is_zero(v0)) {
    +        m3 = Libbcmath.bc_init_num() // bc_copy_num (BCG(_zero_));
    +      } else {
    +        // m3 = Libbcmath.bc_init_num(); //allow pass-by-ref
    +        m3 = Libbcmath._bc_rec_mul(u0, u0.n_len, v0, v0.n_len, 0)
    +      }
    +
    +      // Initialize product
    +      prodlen = ulen + vlen + 1
    +      prod = Libbcmath.bc_new_num(prodlen, 0)
    +
    +      if (!m1zero) {
    +        Libbcmath._bc_shift_addsub(prod, m1, 2 * n, 0)
    +        Libbcmath._bc_shift_addsub(prod, m1, n, 0)
    +      }
    +      Libbcmath._bc_shift_addsub(prod, m3, n, 0)
    +      Libbcmath._bc_shift_addsub(prod, m3, 0, 0)
    +      Libbcmath._bc_shift_addsub(prod, m2, n, d1.n_sign !== d2.n_sign)
    +
    +      return prod
    +      // Now clean up!
    +      // bc_free_num (&u1);
    +      // bc_free_num (&u0);
    +      // bc_free_num (&v1);
    +      // bc_free_num (&m1);
    +      // bc_free_num (&v0);
    +      // bc_free_num (&m2);
    +      // bc_free_num (&m3);
    +      // bc_free_num (&d1);
    +      // bc_free_num (&d2);
    +    },
    +
    +    /**
    +     *
    +     * @param {bc_num} n1
    +     * @param {bc_num} n2
    +     * @param {boolean} useSign
    +     * @param {boolean} ignoreLast
    +     * @return -1, 0, 1 (see bc_compare)
    +     */
    +    _bc_do_compare: function (n1: BcNum, n2: BcNum, useSign: boolean, ignoreLast: boolean): -1 | 0 | 1 {
    +      let n1ptr
    +      let n2ptr // int
    +      let count // int
    +      // First, compare signs.
    +      if (useSign && n1.n_sign !== n2.n_sign) {
    +        if (n1.n_sign === Libbcmath.PLUS) {
    +          return 1 // Positive N1 > Negative N2
    +        } else {
    +          return -1 // Negative N1 < Positive N1
    +        }
    +      }
    +
    +      // Now compare the magnitude.
    +      if (n1.n_len !== n2.n_len) {
    +        if (n1.n_len > n2.n_len) {
    +          // Magnitude of n1 > n2.
    +          if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    +            return 1
    +          } else {
    +            return -1
    +          }
    +        } else {
    +          // Magnitude of n1 < n2.
    +          if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    +            return -1
    +          } else {
    +            return 1
    +          }
    +        }
    +      }
    +
    +      /* If we get here, they have the same number of integer digits.
    +     check the integer part and the equal length part of the fraction. */
    +      count = n1.n_len + Math.min(n1.n_scale, n2.n_scale)
    +      n1ptr = 0
    +      n2ptr = 0
    +
    +      while (count > 0 && n1.n_value[n1ptr] === n2.n_value[n2ptr]) {
    +        n1ptr++
    +        n2ptr++
    +        count--
    +      }
    +
    +      if (ignoreLast && count === 1 && n1.n_scale === n2.n_scale) {
    +        return 0
    +      }
    +
    +      if (count !== 0) {
    +        if (digitAt(n1.n_value, n1ptr) > digitAt(n2.n_value, n2ptr)) {
    +          // Magnitude of n1 > n2.
    +          if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    +            return 1
    +          } else {
    +            return -1
    +          }
    +        } else {
    +          // Magnitude of n1 < n2.
    +          if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    +            return -1
    +          } else {
    +            return 1
    +          }
    +        }
    +      }
    +
    +      // They are equal up to the last part of the equal part of the fraction.
    +      if (n1.n_scale !== n2.n_scale) {
    +        if (n1.n_scale > n2.n_scale) {
    +          for (count = n1.n_scale - n2.n_scale; count > 0; count--) {
    +            if (digitAt(n1.n_value, n1ptr++) !== 0) {
    +              // Magnitude of n1 > n2.
    +              if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    +                return 1
    +              } else {
    +                return -1
    +              }
    +            }
    +          }
    +        } else {
    +          for (count = n2.n_scale - n1.n_scale; count > 0; count--) {
    +            if (digitAt(n2.n_value, n2ptr++) !== 0) {
    +              // Magnitude of n1 < n2.
    +              if (!useSign || n1.n_sign === Libbcmath.PLUS) {
    +                return -1
    +              } else {
    +                return 1
    +              }
    +            }
    +          }
    +        }
    +      }
    +
    +      // They must be equal!
    +      return 0
    +    },
    +
    +    /* Here is the full subtract routine that takes care of negative numbers.
    +   N2 is subtracted from N1 and the result placed in RESULT.  SCALE_MIN
    +   is the minimum scale for the result. */
    +    bc_sub: function (n1: BcNum, n2: BcNum, scaleMin: number): BcNum {
    +      let diff: BcNum = Libbcmath.bc_init_num() // bc_num
    +      let cmpRes
    +      let resScale // int
    +      if (n1.n_sign !== n2.n_sign) {
    +        diff = Libbcmath._bc_do_add(n1, n2, scaleMin)
    +        diff.n_sign = n1.n_sign
    +      } else {
    +        // subtraction must be done.
    +        // Compare magnitudes.
    +        cmpRes = Libbcmath._bc_do_compare(n1, n2, false, false)
    +        switch (cmpRes) {
    +          case -1:
    +            // n1 is less than n2, subtract n1 from n2.
    +            diff = Libbcmath._bc_do_sub(n2, n1, scaleMin)
    +            diff.n_sign = n2.n_sign === Libbcmath.PLUS ? Libbcmath.MINUS : Libbcmath.PLUS
    +            break
    +          case 0:
    +            // They are equal! return zero!
    +            resScale = Libbcmath.MAX(scaleMin, Libbcmath.MAX(n1.n_scale, n2.n_scale))
    +            diff = Libbcmath.bc_new_num(1, resScale)
    +            Libbcmath.memset(diff.n_value, 0, 0, resScale + 1)
    +            break
    +          case 1:
    +            // n2 is less than n1, subtract n2 from n1.
    +            diff = Libbcmath._bc_do_sub(n1, n2, scaleMin)
    +            diff.n_sign = n1.n_sign
    +            break
    +        }
    +      }
    +
    +      // Clean up and return.
    +      // bc_free_num (result);
    +      //* result = diff;
    +      return diff
    +    },
    +
    +    _bc_do_add: function (n1: BcNum, n2: BcNum, scaleMin: number): BcNum {
    +      let sum: BcNum // bc_num
    +      let sumScale
    +      let sumDigits // int
    +      let n1ptr
    +      let n2ptr
    +      let sumptr // int
    +      let carry
    +      let n1bytes
    +      let n2bytes // int
    +      let tmp // int
    +
    +      // Prepare sum.
    +      sumScale = Libbcmath.MAX(n1.n_scale, n2.n_scale)
    +      sumDigits = Libbcmath.MAX(n1.n_len, n2.n_len) + 1
    +      sum = Libbcmath.bc_new_num(sumDigits, Libbcmath.MAX(sumScale, scaleMin))
    +
    +      // Start with the fraction part.  Initialize the pointers.
    +      n1bytes = n1.n_scale
    +      n2bytes = n2.n_scale
    +      n1ptr = n1.n_len + n1bytes - 1
    +      n2ptr = n2.n_len + n2bytes - 1
    +      sumptr = sumScale + sumDigits - 1
    +
    +      // Add the fraction part.  First copy the longer fraction
    +      // (ie when adding 1.2345 to 1 we know .2345 is correct already) .
    +      if (n1bytes !== n2bytes) {
    +        if (n1bytes > n2bytes) {
    +          // n1 has more dp then n2
    +          while (n1bytes > n2bytes) {
    +            sum.n_value[sumptr--] = digitAt(n1.n_value, n1ptr--)
    +            // *sumptr-- = *n1ptr--;
    +            n1bytes--
    +          }
    +        } else {
    +          // n2 has more dp then n1
    +          while (n2bytes > n1bytes) {
    +            sum.n_value[sumptr--] = digitAt(n2.n_value, n2ptr--)
    +            // *sumptr-- = *n2ptr--;
    +            n2bytes--
    +          }
    +        }
    +      }
    +
    +      // Now add the remaining fraction part and equal size integer parts.
    +      n1bytes += n1.n_len
    +      n2bytes += n2.n_len
    +      carry = 0
    +      while (n1bytes > 0 && n2bytes > 0) {
    +        // add the two numbers together
    +        tmp = digitAt(n1.n_value, n1ptr--) + digitAt(n2.n_value, n2ptr--) + carry
    +        // *sumptr = *n1ptr-- + *n2ptr-- + carry;
    +        // check if they are >= 10 (impossible to be more then 18)
    +        if (tmp >= Libbcmath.BASE) {
    +          carry = 1
    +          tmp -= Libbcmath.BASE // yep, subtract 10, add a carry
    +        } else {
    +          carry = 0
    +        }
    +        sum.n_value[sumptr] = tmp
    +        sumptr--
    +        n1bytes--
    +        n2bytes--
    +      }
    +
    +      // Now add carry the [rest of the] longer integer part.
    +      if (n1bytes === 0) {
    +        // n2 is a bigger number then n1
    +        while (n2bytes-- > 0) {
    +          tmp = digitAt(n2.n_value, n2ptr--) + carry
    +          // *sumptr = *n2ptr-- + carry;
    +          if (tmp >= Libbcmath.BASE) {
    +            carry = 1
    +            tmp -= Libbcmath.BASE
    +          } else {
    +            carry = 0
    +          }
    +          sum.n_value[sumptr--] = tmp
    +        }
    +      } else {
    +        // n1 is bigger then n2..
    +        while (n1bytes-- > 0) {
    +          tmp = digitAt(n1.n_value, n1ptr--) + carry
    +          // *sumptr = *n1ptr-- + carry;
    +          if (tmp >= Libbcmath.BASE) {
    +            carry = 1
    +            tmp -= Libbcmath.BASE
    +          } else {
    +            carry = 0
    +          }
    +          sum.n_value[sumptr--] = tmp
    +        }
    +      }
    +
    +      // Set final carry.
    +      if (carry === 1) {
    +        sum.n_value[sumptr] = digitAt(sum.n_value, sumptr) + 1
    +        // *sumptr += 1;
    +      }
    +
    +      // Adjust sum and return.
    +      Libbcmath._bc_rm_leading_zeros(sum)
    +      return sum
    +    },
    +
    +    /**
    +     * Perform a subtraction
    +     *
    +     * Perform subtraction: N2 is subtracted from N1 and the value is
    +     *  returned.  The signs of N1 and N2 are ignored.  Also, N1 is
    +     *  assumed to be larger than N2.  SCALE_MIN is the minimum scale
    +     *  of the result.
    +     *
    +     * Basic school maths says to subtract 2 numbers..
    +     * 1. make them the same length, the decimal places, and the integer part
    +     * 2. start from the right and subtract the two numbers from each other
    +     * 3. if the sum of the 2 numbers < 0, carry -1 to the next set and add 10
    +     * (ie 18 > carry 1 becomes 8). thus 0.9 + 0.9 = 1.8
    +     *
    +     * @param {bc_num} n1
    +     * @param {bc_num} n2
    +     * @param {int} scaleMin
    +     * @return bc_num
    +     */
    +    _bc_do_sub: function (n1: BcNum, n2: BcNum, scaleMin: number): BcNum {
    +      let diff: BcNum // bc_num
    +      let diffScale
    +      let diffLen // int
    +      let minScale
    +      let minLen // int
    +      let n1ptr
    +      let n2ptr
    +      let diffptr // int
    +      let borrow
    +      let count
    +      let val // int
    +      // Allocate temporary storage.
    +      diffLen = Libbcmath.MAX(n1.n_len, n2.n_len)
    +      diffScale = Libbcmath.MAX(n1.n_scale, n2.n_scale)
    +      minLen = Libbcmath.MIN(n1.n_len, n2.n_len)
    +      minScale = Libbcmath.MIN(n1.n_scale, n2.n_scale)
    +      diff = Libbcmath.bc_new_num(diffLen, Libbcmath.MAX(diffScale, scaleMin))
    +
    +      /* Not needed?
    +      // Zero extra digits made by scaleMin.
    +      if (scaleMin > diffScale) {
    +        diffptr = (char *) (diff->n_value + diffLen + diffScale);
    +        for (count = scaleMin - diffScale; count > 0; count--) {
    +          *diffptr++ = 0;
    +        }
    +      }
    +      */
    +
    +      // Initialize the subtract.
    +      n1ptr = n1.n_len + n1.n_scale - 1
    +      n2ptr = n2.n_len + n2.n_scale - 1
    +      diffptr = diffLen + diffScale - 1
    +
    +      // Subtract the numbers.
    +      borrow = 0
    +
    +      // Take care of the longer scaled number.
    +      if (n1.n_scale !== minScale) {
    +        // n1 has the longer scale
    +        for (count = n1.n_scale - minScale; count > 0; count--) {
    +          diff.n_value[diffptr--] = digitAt(n1.n_value, n1ptr--)
    +          // *diffptr-- = *n1ptr--;
    +        }
    +      } else {
    +        // n2 has the longer scale
    +        for (count = n2.n_scale - minScale; count > 0; count--) {
    +          val = 0 - digitAt(n2.n_value, n2ptr--) - borrow
    +          // val = - *n2ptr-- - borrow;
    +          if (val < 0) {
    +            val += Libbcmath.BASE
    +            borrow = 1
    +          } else {
    +            borrow = 0
    +          }
    +          diff.n_value[diffptr--] = val
    +          //* diffptr-- = val;
    +        }
    +      }
    +
    +      // Now do the equal length scale and integer parts.
    +      for (count = 0; count < minLen + minScale; count++) {
    +        val = digitAt(n1.n_value, n1ptr--) - digitAt(n2.n_value, n2ptr--) - borrow
    +        // val = *n1ptr-- - *n2ptr-- - borrow;
    +        if (val < 0) {
    +          val += Libbcmath.BASE
    +          borrow = 1
    +        } else {
    +          borrow = 0
    +        }
    +        diff.n_value[diffptr--] = val
    +        //* diffptr-- = val;
    +      }
    +
    +      // If n1 has more digits then n2, we now do that subtract.
    +      if (diffLen !== minLen) {
    +        for (count = diffLen - minLen; count > 0; count--) {
    +          val = digitAt(n1.n_value, n1ptr--) - borrow
    +          // val = *n1ptr-- - borrow;
    +          if (val < 0) {
    +            val += Libbcmath.BASE
    +            borrow = 1
    +          } else {
    +            borrow = 0
    +          }
    +          diff.n_value[diffptr--] = val
    +        }
    +      }
    +
    +      // Clean up and return.
    +      Libbcmath._bc_rm_leading_zeros(diff)
    +      return diff
    +    },
    +
    +    /**
    +     *
    +     * @param {int} length
    +     * @param {int} scale
    +     * @return bc_num
    +     */
    +    bc_new_num: function (length: number, scale: number): BcNum {
    +      let temp: BcNum // bc_num
    +      temp = Libbcmath.bc_num()
    +      temp.n_sign = Libbcmath.PLUS
    +      temp.n_len = length
    +      temp.n_scale = scale
    +      temp.n_value = Libbcmath.safe_emalloc(1, length + scale, 0)
    +      Libbcmath.memset(temp.n_value, 0, 0, length + scale)
    +      return temp
    +    },
    +
    +    safe_emalloc: function (size: number, len: number, extra: number): number[] {
    +      return new Array(size * len + extra)
    +    },
    +
    +    /**
    +     * Create a new number
    +     */
    +    bc_init_num: function (): BcNum {
    +      return Libbcmath.bc_new_num(1, 0)
    +    },
    +
    +    _bc_rm_leading_zeros: function (num: BcNum) {
    +      // We can move n_value to point to the first non zero digit!
    +      while (num.n_value[0] === 0 && num.n_len > 1) {
    +        num.n_value.shift()
    +        num.n_len--
    +      }
    +    },
    +
    +    /**
    +     * Convert to bc_num detecting scale
    +     */
    +    php_str2num: function (str: string): BcNum {
    +      let p
    +      p = str.indexOf('.')
    +      if (p === -1) {
    +        return Libbcmath.bc_str2num(str, 0)
    +      } else {
    +        return Libbcmath.bc_str2num(str, str.length - p)
    +      }
    +    },
    +
    +    CH_VAL: function (c: string) {
    +      return Number(c) - 0 // ??
    +    },
    +
    +    BCD_CHAR: function (d: number) {
    +      return String(d) + '0' // ??
    +    },
    +
    +    isdigit: function (c: string) {
    +      return isNaN(parseInt(c, 10))
    +    },
    +
    +    bc_str2num: function (strIn: string, scale: number): BcNum {
    +      let str: string[]
    +      let num: BcNum
    +      let ptr: number
    +      let digits: number
    +      let strscale: number
    +      let zeroInt: boolean
    +      let nptr: number
    +      // remove any non-expected characters
    +      // Check for valid number and count digits.
    +
    +      str = strIn.split('') // convert to array
    +      ptr = 0 // str
    +      digits = 0
    +      strscale = 0
    +      zeroInt = false
    +      if (str[ptr] === '+' || str[ptr] === '-') {
    +        ptr++ // Sign
    +      }
    +      while (str[ptr] === '0') {
    +        ptr++ // Skip leading zeros.
    +      }
    +      // while (Libbcmath.isdigit(str[ptr])) {
    +      while (str[ptr] !== undefined && /\d/.test(str[ptr] ?? '')) {
    +        // Libbcmath.isdigit(str[ptr])) {
    +        ptr++
    +        digits++ // digits
    +      }
    +
    +      if (str[ptr] === '.') {
    +        ptr++ // decimal point
    +      }
    +      // while (Libbcmath.isdigit(str[ptr])) {
    +      while (str[ptr] !== undefined && /\d/.test(str[ptr] ?? '')) {
    +        // Libbcmath.isdigit(str[ptr])) {
    +        ptr++
    +        strscale++ // digits
    +      }
    +
    +      if (str[ptr] || digits + strscale === 0) {
    +        // invalid number, return 0
    +        return Libbcmath.bc_init_num()
    +        //* num = bc_copy_num (BCG(_zero_));
    +      }
    +
    +      // Adjust numbers and allocate storage and initialize fields.
    +      strscale = Libbcmath.MIN(strscale, scale)
    +      if (digits === 0) {
    +        zeroInt = true
    +        digits = 1
    +      }
    +
    +      num = Libbcmath.bc_new_num(digits, strscale)
    +
    +      // Build the whole number.
    +      ptr = 0 // str
    +      if (str[ptr] === '-') {
    +        num.n_sign = Libbcmath.MINUS
    +        // (*num)->n_sign = MINUS;
    +        ptr++
    +      } else {
    +        num.n_sign = Libbcmath.PLUS
    +        // (*num)->n_sign = PLUS;
    +        if (str[ptr] === '+') {
    +          ptr++
    +        }
    +      }
    +      while (str[ptr] === '0') {
    +        ptr++ // Skip leading zeros.
    +      }
    +
    +      nptr = 0 // (*num)->n_value;
    +      if (zeroInt) {
    +        num.n_value[nptr++] = 0
    +        digits = 0
    +      }
    +      for (; digits > 0; digits--) {
    +        num.n_value[nptr++] = Libbcmath.CH_VAL(str[ptr++] ?? '0')
    +        //* nptr++ = CH_VAL(*ptr++);
    +      }
    +
    +      // Build the fractional part.
    +      if (strscale > 0) {
    +        ptr++ // skip the decimal point!
    +        for (; strscale > 0; strscale--) {
    +          num.n_value[nptr++] = Libbcmath.CH_VAL(str[ptr++] ?? '0')
    +        }
    +      }
    +
    +      return num
    +    },
    +
    +    cint: function (v: PhpInput) {
    +      if (typeof v === 'undefined') {
    +        v = 0
    +      }
    +      let x = Number.parseInt(String(v), 10)
    +      if (isNaN(x)) {
    +        x = 0
    +      }
    +      return x
    +    },
    +
    +    /**
    +     * Basic min function
    +     * @param {int} a
    +     * @param {int} b
    +     */
    +    MIN: function (a: number, b: number) {
    +      return a > b ? b : a
    +    },
    +
    +    /**
    +     * Basic max function
    +     * @param {int} a
    +     * @param {int} b
    +     */
    +    MAX: function (a: number, b: number) {
    +      return a > b ? a : b
    +    },
    +
    +    /**
    +     * Basic odd function
    +     * @param {int} a
    +     */
    +    ODD: function (a: number) {
    +      return a & 1
    +    },
    +
    +    /**
    +     * replicate c function
    +     * @param {array} r     return (by reference)
    +     * @param {int} ptr
    +     * @param {string} chr    char to fill
    +     * @param {int} len       length to fill
    +     */
    +    memset: function (r: number[], ptr: number, chr: number, len: number) {
    +      let i
    +      for (i = 0; i < len; i++) {
    +        r[ptr + i] = chr
    +      }
    +    },
    +
    +    /**
    +     * Replacement c function
    +     * Obviously can't work like c does, so we've added an "offset"
    +     * param so you could do memcpy(dest+1, src, len) as memcpy(dest, 1, src, len)
    +     * Also only works on arrays
    +     */
    +    memcpy: function (dest: number[], ptr: number, src: number[], srcptr: number, len: number) {
    +      let i
    +      for (i = 0; i < len; i++) {
    +        dest[ptr + i] = src[srcptr + i] ?? 0
    +      }
    +      return true
    +    },
    +
    +    /**
    +     * Determine if the number specified is zero or not
    +     * @param {bc_num} num    number to check
    +     * @return boolean      true when zero, false when not zero.
    +     */
    +    bc_is_zero: function (num: BcNum) {
    +      let count // int
    +      let nptr // int
    +      // Quick check.
    +      // if (num === BCG(_zero_)) return TRUE;
    +      // Initialize
    +      count = num.n_len + num.n_scale
    +      nptr = 0 // num->n_value;
    +      // The check
    +      while (count > 0 && num.n_value[nptr++] === 0) {
    +        count--
    +      }
    +
    +      if (count !== 0) {
    +        return false
    +      } else {
    +        return true
    +      }
    +    },
    +
    +    bc_out_of_memory: function () {
    +      throw new Error('(BC) Out of memory')
    +    },
    +  }
    +  return Libbcmath
    +}
    +
    +export function _bc(): ReturnType<typeof createBcLibrary> {
    +  // original by: lmeyrick (https://sourceforge.net/projects/bcmath-js/)
    +  // improved by: Brett Zamir (https://brett-zamir.me)
    +  return createBcLibrary()
    +}
    
  • src/php/_helpers/_callbackResolver.ts+79 0 added
    @@ -0,0 +1,79 @@
    +import { getPhpGlobalEntry, getPhpObjectEntry } from './_phpRuntimeState.ts'
    +import {
    +  isObjectLike,
    +  isPhpCallable,
    +  type NumericLike,
    +  type PhpCallable,
    +  type PhpCallableArgs,
    +  type PhpCallableDescriptor,
    +  type PhpInput,
    +} from './_phpTypes.ts'
    +
    +type CallbackValue = PhpInput
    +
    +interface CallbackResolverOptions {
    +  invalidMessage: string
    +  missingScopeMessage?: (scopeName: string) => string
    +}
    +
    +interface ResolvedCallback<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = CallbackValue> {
    +  fn: PhpCallable<TArgs, TResult>
    +  scope: CallbackValue
    +}
    +
    +export function resolvePhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = CallbackValue>(
    +  callback: PhpCallableDescriptor<TArgs, TResult>,
    +  options: CallbackResolverOptions,
    +): ResolvedCallback<TArgs, TResult> {
    +  if (isPhpCallable<TArgs, TResult>(callback)) {
    +    return { fn: callback, scope: null }
    +  }
    +
    +  if (typeof callback === 'string') {
    +    const candidate = getPhpGlobalEntry(callback)
    +    if (isPhpCallable<TArgs, TResult>(candidate)) {
    +      return { fn: candidate, scope: null }
    +    }
    +    throw new Error(options.invalidMessage)
    +  }
    +
    +  if (Array.isArray(callback) && callback.length >= 2) {
    +    const scopeDescriptor = callback[0]
    +    const callableDescriptor = callback[1]
    +
    +    let scope: CallbackValue
    +    if (typeof scopeDescriptor === 'string') {
    +      scope = getPhpGlobalEntry(scopeDescriptor)
    +      if (typeof scope === 'undefined' && options.missingScopeMessage) {
    +        throw new Error(options.missingScopeMessage(scopeDescriptor))
    +      }
    +    } else {
    +      scope = scopeDescriptor
    +    }
    +
    +    if (isPhpCallable<TArgs, TResult>(callableDescriptor)) {
    +      return { fn: callableDescriptor, scope }
    +    }
    +
    +    if (typeof callableDescriptor === 'string' && (isObjectLike(scope) || typeof scope === 'function')) {
    +      const candidate = getPhpObjectEntry(scope, callableDescriptor)
    +      if (isPhpCallable<TArgs, TResult>(candidate)) {
    +        return { fn: candidate, scope }
    +      }
    +    }
    +  }
    +
    +  throw new Error(options.invalidMessage)
    +}
    +
    +export function resolveNumericComparator<
    +  TLeft extends CallbackValue = CallbackValue,
    +  TRight extends CallbackValue = CallbackValue,
    +>(
    +  callback: PhpCallableDescriptor<[TLeft, TRight], NumericLike>,
    +  invalidMessage: string,
    +): (left: TLeft, right: TRight) => number {
    +  const resolved = resolvePhpCallable<[TLeft, TRight], NumericLike>(callback, { invalidMessage })
    +
    +  return (left: TLeft, right: TRight): number => Number(resolved.fn.apply(resolved.scope, [left, right]))
    +}
    
  • src/php/_helpers/_ctypePattern.ts+32 0 added
    @@ -0,0 +1,32 @@
    +import { getPhpLocaleGroup } from './_phpRuntimeState.ts'
    +
    +const defaultCtypePatterns: { [key: string]: RegExp } = {
    +  an: /^[A-Za-z\d]+$/g,
    +  al: /^[A-Za-z]+$/g,
    +  // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional for LC_CTYPE control character class
    +  ct: /^[\u0000-\u001F\u007F]+$/g,
    +  dg: /^[\d]+$/g,
    +  gr: /^[\u0021-\u007E]+$/g,
    +  lw: /^[a-z]+$/g,
    +  pr: /^[\u0020-\u007E]+$/g,
    +  pu: /^[\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E]+$/g,
    +  sp: /^[\f\n\r\t\v ]+$/g,
    +  up: /^[A-Z]+$/g,
    +  xd: /^[A-Fa-f\d]+$/g,
    +}
    +
    +export function getCtypePattern(key: string): RegExp | undefined {
    +  const ctypeGroup = getPhpLocaleGroup('LC_CTYPE', 'LC_CTYPE')
    +  if (!ctypeGroup) {
    +    const fallbackPattern = defaultCtypePatterns[key]
    +    return fallbackPattern ? new RegExp(fallbackPattern) : undefined
    +  }
    +
    +  const pattern = ctypeGroup[key]
    +  if (pattern instanceof RegExp) {
    +    return new RegExp(pattern)
    +  }
    +
    +  const fallbackPattern = defaultCtypePatterns[key]
    +  return fallbackPattern ? new RegExp(fallbackPattern) : undefined
    +}
    
  • src/php/_helpers/index.js+0 4 removed
    @@ -1,4 +0,0 @@
    -module.exports._bc = require('./_bc')
    -module.exports._phpCastString = require('./_phpCastString')
    -module.exports._php_cast_float = require('./_php_cast_float')
    -module.exports._php_cast_int = require('./_php_cast_int')
    
  • src/php/_helpers/index.ts+17 0 added
    @@ -0,0 +1,17 @@
    +export { getArrayLikeLength, getEntryAtCursor, getPointerState } from './_arrayPointers.ts'
    +export { _bc } from './_bc.ts'
    +export { resolveNumericComparator, resolvePhpCallable } from './_callbackResolver.ts'
    +export { _php_cast_float } from './_php_cast_float.ts'
    +export { _php_cast_int } from './_php_cast_int.ts'
    +export { _phpCastString } from './_phpCastString.ts'
    +export { ensurePhpRuntimeState, setPhpLocaleDefault } from './_phpRuntimeState.ts'
    +export {
    +  assertIsObjectLike,
    +  assertIsPhpCallable,
    +  isNumericLike,
    +  isObjectLike,
    +  isPhpArrayObject,
    +  isPhpCallable,
    +  isPhpScalar,
    +  toPhpArrayObject,
    +} from './_phpTypes.ts'
    
  • src/php/_helpers/_php_cast_float.js+0 46 removed
    @@ -1,46 +0,0 @@
    -module.exports = function _php_cast_float(value) {
    -  // original by: Rafał Kukawski
    -  //   example 1: _php_cast_float(false)
    -  //   returns 1: 0
    -  //   example 2: _php_cast_float(true)
    -  //   returns 2: 1
    -  //   example 3: _php_cast_float(0)
    -  //   returns 3: 0
    -  //   example 4: _php_cast_float(1)
    -  //   returns 4: 1
    -  //   example 5: _php_cast_float(3.14)
    -  //   returns 5: 3.14
    -  //   example 6: _php_cast_float('')
    -  //   returns 6: 0
    -  //   example 7: _php_cast_float('0')
    -  //   returns 7: 0
    -  //   example 8: _php_cast_float('abc')
    -  //   returns 8: 0
    -  //   example 9: _php_cast_float(null)
    -  //   returns 9: 0
    -  //  example 10: _php_cast_float(undefined)
    -  //  returns 10: 0
    -  //  example 11: _php_cast_float('123abc')
    -  //  returns 11: 123
    -  //  example 12: _php_cast_float('123e4')
    -  //  returns 12: 1230000
    -  //  example 13: _php_cast_float(0x200000001)
    -  //  returns 13: 8589934593
    -  //  example 14: _php_cast_float('3.14abc')
    -  //  returns 14: 3.14
    -
    -  const type = typeof value
    -
    -  switch (type) {
    -    case 'number':
    -      return value
    -    case 'string':
    -      return parseFloat(value) || 0
    -    case 'boolean':
    -    // fall through
    -    default:
    -      // PHP docs state, that for types other than string
    -      // conversion is {input type}->int->float
    -      return require('./_php_cast_int')(value)
    -  }
    -}
    
  • src/php/_helpers/_php_cast_float.ts+19 0 added
    @@ -0,0 +1,19 @@
    +import { _php_cast_int as __php_cast_int } from './_php_cast_int.ts'
    +import type { PhpInput } from './_phpTypes.ts'
    +
    +type CastFloatValue = PhpInput
    +
    +export function _php_cast_float(value: CastFloatValue): number {
    +  // original by: Rafał Kukawski
    +
    +  if (typeof value === 'number') {
    +    return value
    +  }
    +  if (typeof value === 'string') {
    +    return parseFloat(value) || 0
    +  }
    +
    +  // PHP docs state, that for types other than string
    +  // conversion is {input type}->int->float
    +  return __php_cast_int(value)
    +}
    
  • src/php/_helpers/_php_cast_int.js+0 52 removed
    @@ -1,52 +0,0 @@
    -module.exports = function _php_cast_int(value) {
    -  // original by: Rafał Kukawski
    -  //   example 1: _php_cast_int(false)
    -  //   returns 1: 0
    -  //   example 2: _php_cast_int(true)
    -  //   returns 2: 1
    -  //   example 3: _php_cast_int(0)
    -  //   returns 3: 0
    -  //   example 4: _php_cast_int(1)
    -  //   returns 4: 1
    -  //   example 5: _php_cast_int(3.14)
    -  //   returns 5: 3
    -  //   example 6: _php_cast_int('')
    -  //   returns 6: 0
    -  //   example 7: _php_cast_int('0')
    -  //   returns 7: 0
    -  //   example 8: _php_cast_int('abc')
    -  //   returns 8: 0
    -  //   example 9: _php_cast_int(null)
    -  //   returns 9: 0
    -  //  example 10: _php_cast_int(undefined)
    -  //  returns 10: 0
    -  //  example 11: _php_cast_int('123abc')
    -  //  returns 11: 123
    -  //  example 12: _php_cast_int('123e4')
    -  //  returns 12: 123
    -  //  example 13: _php_cast_int(0x200000001)
    -  //  returns 13: 8589934593
    -
    -  const type = typeof value
    -
    -  switch (type) {
    -    case 'number':
    -      if (isNaN(value) || !isFinite(value)) {
    -        // from PHP 7, NaN and Infinity are casted to 0
    -        return 0
    -      }
    -
    -      return value < 0 ? Math.ceil(value) : Math.floor(value)
    -    case 'string':
    -      return parseInt(value, 10) || 0
    -    case 'boolean':
    -    // fall through
    -    default:
    -      // Behaviour for types other than float, string, boolean
    -      // is undefined and can change any time.
    -      // To not invent complex logic
    -      // that mimics PHP 7.0 behaviour
    -      // casting value->bool->number is used
    -      return +!!value
    -  }
    -}
    
  • src/php/_helpers/_php_cast_int.ts+26 0 added
    @@ -0,0 +1,26 @@
    +import type { PhpInput } from './_phpTypes.ts'
    +
    +type CastIntValue = PhpInput
    +
    +export function _php_cast_int(value: CastIntValue): number {
    +  // original by: Rafał Kukawski
    +
    +  if (typeof value === 'number') {
    +    if (isNaN(value) || !isFinite(value)) {
    +      // from PHP 7, NaN and Infinity are casted to 0
    +      return 0
    +    }
    +
    +    return value < 0 ? Math.ceil(value) : Math.floor(value)
    +  }
    +  if (typeof value === 'string') {
    +    return parseInt(value, 10) || 0
    +  }
    +
    +  // Behaviour for types other than float, string, boolean
    +  // is undefined and can change any time.
    +  // To not invent complex logic
    +  // that mimics PHP 7.0 behaviour
    +  // casting value->bool->number is used
    +  return +!!value
    +}
    
  • src/php/_helpers/_phpCastString.js+0 64 removed
    @@ -1,64 +0,0 @@
    -module.exports = function _phpCastString(value) {
    -  // original by: Rafał Kukawski
    -  //   example 1: _phpCastString(true)
    -  //   returns 1: '1'
    -  //   example 2: _phpCastString(false)
    -  //   returns 2: ''
    -  //   example 3: _phpCastString('foo')
    -  //   returns 3: 'foo'
    -  //   example 4: _phpCastString(0/0)
    -  //   returns 4: 'NAN'
    -  //   example 5: _phpCastString(1/0)
    -  //   returns 5: 'INF'
    -  //   example 6: _phpCastString(-1/0)
    -  //   returns 6: '-INF'
    -  //   example 7: _phpCastString(null)
    -  //   returns 7: ''
    -  //   example 8: _phpCastString(undefined)
    -  //   returns 8: ''
    -  //   example 9: _phpCastString([])
    -  //   returns 9: 'Array'
    -  //  example 10: _phpCastString({})
    -  //  returns 10: 'Object'
    -  //  example 11: _phpCastString(0)
    -  //  returns 11: '0'
    -  //  example 12: _phpCastString(1)
    -  //  returns 12: '1'
    -  //  example 13: _phpCastString(3.14)
    -  //  returns 13: '3.14'
    -
    -  const type = typeof value
    -
    -  switch (type) {
    -    case 'boolean':
    -      return value ? '1' : ''
    -    case 'string':
    -      return value
    -    case 'number':
    -      if (isNaN(value)) {
    -        return 'NAN'
    -      }
    -
    -      if (!isFinite(value)) {
    -        return (value < 0 ? '-' : '') + 'INF'
    -      }
    -
    -      return value + ''
    -    case 'undefined':
    -      return ''
    -    case 'object':
    -      if (Array.isArray(value)) {
    -        return 'Array'
    -      }
    -
    -      if (value !== null) {
    -        return 'Object'
    -      }
    -
    -      return ''
    -    case 'function':
    -    // fall through
    -    default:
    -      throw new Error('Unsupported value type')
    -  }
    -}
    
  • src/php/_helpers/_phpCastString.ts+41 0 added
    @@ -0,0 +1,41 @@
    +import type { PhpInput } from './_phpTypes.ts'
    +
    +type CastStringValue = PhpInput
    +
    +export function _phpCastString(value: CastStringValue): string {
    +  // original by: Rafał Kukawski
    +
    +  if (typeof value === 'boolean') {
    +    return value ? '1' : ''
    +  }
    +  if (typeof value === 'string') {
    +    return value
    +  }
    +  if (typeof value === 'number') {
    +    if (isNaN(value)) {
    +      return 'NAN'
    +    }
    +
    +    if (!isFinite(value)) {
    +      return (value < 0 ? '-' : '') + 'INF'
    +    }
    +
    +    return value + ''
    +  }
    +  if (typeof value === 'undefined') {
    +    return ''
    +  }
    +  if (typeof value === 'object') {
    +    if (Array.isArray(value)) {
    +      return 'Array'
    +    }
    +
    +    if (value !== null) {
    +      return 'Object'
    +    }
    +
    +    return ''
    +  }
    +
    +  throw new Error('Unsupported value type')
    +}
    
  • src/php/_helpers/_phpRuntimeState.ts+283 0 added
    @@ -0,0 +1,283 @@
    +import {
    +  isPhpAssocObject,
    +  isPhpCallable,
    +  type PhpAssoc,
    +  type PhpCallable,
    +  type PhpInput,
    +  type PhpList,
    +} from './_phpTypes.ts'
    +
    +interface IniEntry {
    +  local_value?: PhpInput
    +}
    +
    +type LocaleEntry = PhpAssoc<PhpInput> & {
    +  sorting?: (left: PhpInput, right: PhpInput) => number
    +}
    +type LocaleCategoryMap = PhpAssoc<string | undefined>
    +interface LocutusRuntimeContainer {
    +  php?: PhpAssoc<PhpInput>
    +}
    +
    +interface PhpRuntimeKnownEntryMap {
    +  ini: PhpAssoc<IniEntry | undefined>
    +  locales: PhpAssoc<LocaleEntry | undefined>
    +  localeCategories: LocaleCategoryMap
    +  pointers: PhpList<PhpInput>
    +  locale_default: string
    +  locale: string
    +  uniqidSeed: number
    +  timeoutStatus: boolean
    +  last_error_json: number
    +  strtokleftOver: string
    +}
    +
    +type PhpRuntimeNumberKey = {
    +  [K in keyof PhpRuntimeKnownEntryMap]: PhpRuntimeKnownEntryMap[K] extends number ? K : never
    +}[keyof PhpRuntimeKnownEntryMap]
    +type PhpRuntimeBooleanKey = {
    +  [K in keyof PhpRuntimeKnownEntryMap]: PhpRuntimeKnownEntryMap[K] extends boolean ? K : never
    +}[keyof PhpRuntimeKnownEntryMap]
    +type PhpRuntimeStringKey = {
    +  [K in keyof PhpRuntimeKnownEntryMap]: PhpRuntimeKnownEntryMap[K] extends string ? K : never
    +}[keyof PhpRuntimeKnownEntryMap]
    +
    +interface PhpGlobalProcessLike {
    +  env?: PhpAssoc<string | undefined>
    +}
    +
    +interface PhpGlobalBufferLike {
    +  from?: (...args: PhpInput[]) => PhpInput
    +}
    +
    +interface PhpGlobalKnownEntryMap {
    +  process: PhpGlobalProcessLike
    +  Buffer: PhpGlobalBufferLike
    +}
    +
    +type GlobalWithLocutus = {
    +  $locutus?: LocutusRuntimeContainer
    +  [key: string]: PhpInput
    +}
    +
    +type PhpObjectEntryContainer = object | PhpCallable
    +
    +export interface PhpRuntimeState {
    +  ini: PhpAssoc<IniEntry | undefined>
    +  locales: PhpAssoc<LocaleEntry | undefined>
    +  localeCategories: LocaleCategoryMap
    +  pointers: PhpList<PhpInput>
    +  locale_default: string | undefined
    +}
    +
    +const isIniBag = (value: PhpInput): value is PhpAssoc<IniEntry | undefined> =>
    +  isPhpAssocObject<IniEntry | undefined>(value)
    +
    +const isLocaleBag = (value: PhpInput): value is PhpAssoc<LocaleEntry | undefined> =>
    +  isPhpAssocObject<LocaleEntry | undefined>(value)
    +
    +const isLocaleCategoryBag = (value: PhpInput): value is LocaleCategoryMap => isPhpAssocObject<string | undefined>(value)
    +
    +const globalContext: GlobalWithLocutus =
    +  typeof window === 'object' && window !== null ? window : typeof global === 'object' && global !== null ? global : {}
    +
    +const ensurePhpRuntimeObject = (): PhpAssoc<PhpInput> => {
    +  let locutus = globalContext.$locutus
    +  if (typeof locutus !== 'object' || locutus === null) {
    +    locutus = {}
    +    globalContext.$locutus = locutus
    +  }
    +
    +  let php = locutus.php
    +  if (typeof php !== 'object' || php === null) {
    +    php = {}
    +    locutus.php = php
    +  }
    +
    +  return php
    +}
    +
    +export function ensurePhpRuntimeState(): PhpRuntimeState {
    +  const php = ensurePhpRuntimeObject()
    +  const iniValue = php.ini
    +  const localesValue = php.locales
    +  const localeCategoriesValue = php.localeCategories
    +  const pointersValue = php.pointers
    +
    +  const ini = isIniBag(iniValue) ? iniValue : {}
    +  const locales = isLocaleBag(localesValue) ? localesValue : {}
    +  const localeCategories = isLocaleCategoryBag(localeCategoriesValue) ? localeCategoriesValue : {}
    +  const pointers: PhpList<PhpInput> = Array.isArray(pointersValue) ? pointersValue : []
    +
    +  if (iniValue !== ini) {
    +    php.ini = ini
    +  }
    +  if (localesValue !== locales) {
    +    php.locales = locales
    +  }
    +  if (localeCategoriesValue !== localeCategories) {
    +    php.localeCategories = localeCategories
    +  }
    +  if (pointersValue !== pointers) {
    +    php.pointers = pointers
    +  }
    +
    +  const localeDefaultValue = php.locale_default
    +  const localeDefault = typeof localeDefaultValue === 'string' ? localeDefaultValue : undefined
    +
    +  return {
    +    ini,
    +    locales,
    +    localeCategories,
    +    pointers,
    +    locale_default: localeDefault,
    +  }
    +}
    +
    +export function setPhpLocaleDefault(localeDefault: string): void {
    +  const php = ensurePhpRuntimeObject()
    +  php.locale_default = localeDefault
    +}
    +
    +export function getPhpRuntimeEntry<TKey extends keyof PhpRuntimeKnownEntryMap>(
    +  key: TKey,
    +): PhpRuntimeKnownEntryMap[TKey] | undefined
    +export function getPhpRuntimeEntry(key: string): PhpInput | undefined
    +export function getPhpRuntimeEntry(key: string): PhpInput | undefined {
    +  const php = ensurePhpRuntimeObject()
    +  const value = php[key]
    +  return typeof value === 'undefined' ? undefined : value
    +}
    +
    +export function setPhpRuntimeEntry<TKey extends keyof PhpRuntimeKnownEntryMap>(
    +  key: TKey,
    +  value: PhpRuntimeKnownEntryMap[TKey],
    +): void
    +export function setPhpRuntimeEntry(key: string, value: PhpInput): void
    +export function setPhpRuntimeEntry(key: string, value: PhpInput): void {
    +  const php = ensurePhpRuntimeObject()
    +  php[key] = value
    +}
    +
    +export function getPhpRuntimeNumber(key: PhpRuntimeNumberKey, fallback: number): number
    +export function getPhpRuntimeNumber(key: string, fallback: number): number
    +export function getPhpRuntimeNumber(key: string, fallback: number): number {
    +  const value = getPhpRuntimeEntry(key)
    +  return typeof value === 'number' ? value : fallback
    +}
    +
    +export function getPhpRuntimeBoolean(key: PhpRuntimeBooleanKey, fallback: boolean): boolean
    +export function getPhpRuntimeBoolean(key: string, fallback: boolean): boolean
    +export function getPhpRuntimeBoolean(key: string, fallback: boolean): boolean {
    +  const value = getPhpRuntimeEntry(key)
    +  return typeof value === 'boolean' ? value : fallback
    +}
    +
    +export function getPhpRuntimeString(key: PhpRuntimeStringKey, fallback: string): string
    +export function getPhpRuntimeString(key: string, fallback: string): string
    +export function getPhpRuntimeString(key: string, fallback: string): string {
    +  const value = getPhpRuntimeEntry(key)
    +  return typeof value === 'string' ? value : fallback
    +}
    +
    +export function getPhpGlobalEntry<TKey extends keyof PhpGlobalKnownEntryMap>(
    +  key: TKey,
    +): PhpGlobalKnownEntryMap[TKey] | undefined
    +export function getPhpGlobalEntry(key: string): PhpInput | undefined
    +export function getPhpGlobalEntry(key: string): PhpInput | undefined {
    +  const value = globalContext[key]
    +  return typeof value === 'undefined' ? undefined : value
    +}
    +
    +export function setPhpGlobalEntry(key: string, value: PhpInput): void {
    +  globalContext[key] = value
    +}
    +
    +export function getPhpGlobalScope(): PhpAssoc<PhpInput> {
    +  return globalContext
    +}
    +
    +export function getPhpGlobalCallable<TArgs extends PhpInput[] = PhpInput[], TResult = PhpInput>(
    +  key: string,
    +): PhpCallable<TArgs, TResult> | undefined {
    +  const value = getPhpGlobalEntry(key)
    +  return isPhpCallable<TArgs, TResult>(value) ? value : undefined
    +}
    +
    +export function getPhpObjectEntry(value: PhpInput, key: string): PhpInput | undefined {
    +  if ((typeof value !== 'object' && typeof value !== 'function') || value === null) {
    +    return undefined
    +  }
    +
    +  let current: object | null = value
    +  while (current) {
    +    const descriptor = Object.getOwnPropertyDescriptor(current, key)
    +    if (descriptor) {
    +      if (typeof descriptor.get === 'function') {
    +        const getterValue = descriptor.get.call(value)
    +        return typeof getterValue === 'undefined' ? undefined : getterValue
    +      }
    +      const directValue = descriptor.value
    +      return typeof directValue === 'undefined' ? undefined : directValue
    +    }
    +    current = Object.getPrototypeOf(current)
    +  }
    +
    +  return undefined
    +}
    +
    +export function setPhpObjectEntry(value: PhpInput, key: string, entry: PhpInput): boolean {
    +  if ((typeof value !== 'object' && typeof value !== 'function') || value === null) {
    +    return false
    +  }
    +
    +  const container: PhpObjectEntryContainer = value
    +  let current: object | null = container
    +  while (current) {
    +    const descriptor = Object.getOwnPropertyDescriptor(current, key)
    +    if (descriptor) {
    +      if (typeof descriptor.set === 'function') {
    +        descriptor.set.call(container, entry)
    +        return true
    +      }
    +      if (descriptor.writable) {
    +        Object.defineProperty(container, key, {
    +          configurable: descriptor.configurable ?? true,
    +          enumerable: descriptor.enumerable ?? true,
    +          value: entry,
    +          writable: true,
    +        })
    +        return true
    +      }
    +      return false
    +    }
    +    current = Object.getPrototypeOf(current)
    +  }
    +
    +  Object.defineProperty(container, key, {
    +    configurable: true,
    +    enumerable: true,
    +    value: entry,
    +    writable: true,
    +  })
    +  return true
    +}
    +
    +function getPhpLocaleEntry(category: string): LocaleEntry | undefined {
    +  const runtime = ensurePhpRuntimeState()
    +  const localeName = runtime.localeCategories[category]
    +  if (typeof localeName !== 'string') {
    +    return undefined
    +  }
    +  const localeEntry = runtime.locales[localeName]
    +  return isPhpAssocObject(localeEntry) ? localeEntry : undefined
    +}
    +
    +export function getPhpLocaleGroup(category: string, groupKey: string): PhpAssoc<PhpInput> | undefined {
    +  const localeEntry = getPhpLocaleEntry(category)
    +  if (!localeEntry) {
    +    return undefined
    +  }
    +  const group = localeEntry[groupKey]
    +  return isPhpAssocObject(group) ? group : undefined
    +}
    
  • src/php/_helpers/_phpTypes.ts+179 0 added
    @@ -0,0 +1,179 @@
    +export type PhpNullish = null | undefined
    +export type PhpInput = {} | PhpNullish
    +
    +export type PhpScalar = string | number | boolean
    +export type PhpLiteral = PhpScalar | null
    +export type PhpPrimitive = PhpScalar | bigint
    +export type PhpKey = string | number
    +export type NumericLike = number | bigint | string
    +export type StringLike = string | number | boolean | bigint
    +export type PhpStringish = StringLike | PhpNullish
    +
    +export type PhpList<T = PhpInput> = T[]
    +export type PhpAssoc<T = PhpInput> = { [key: string]: T }
    +export type PhpContainer<T = PhpInput> = PhpList<T> | PhpAssoc<T>
    +export type PhpArrayLike<T = PhpInput> = PhpList<T> | PhpAssoc<T>
    +export interface PhpRecursiveAssoc {
    +  [key: string]: PhpRecursiveValue
    +}
    +export interface PhpRecursiveList extends Array<PhpRecursiveValue> {}
    +export type PhpRecursiveValue = PhpPrimitive | PhpNullish | PhpRecursiveList | PhpRecursiveAssoc
    +export type PhpFunctionValue = (...args: PhpInput[]) => PhpInput
    +export interface PhpRuntimeAssoc {
    +  [key: string]: PhpRuntimeValue
    +}
    +export interface PhpRuntimeList extends Array<PhpRuntimeValue> {}
    +export type PhpRuntimeValue = PhpPrimitive | PhpNullish | PhpRuntimeList | PhpRuntimeAssoc | PhpFunctionValue
    +export type PhpReadonlyList<T = PhpInput> = readonly T[]
    +export type PhpReadonlyAssoc<T = PhpInput> = Readonly<PhpAssoc<T>>
    +export type PhpReadonlyArrayLike<T = PhpInput> = PhpReadonlyList<T> | PhpReadonlyAssoc<T>
    +
    +export const entriesOfPhpAssoc = <T>(value: PhpAssoc<T>): Array<[string, T]> => {
    +  return Object.entries(value)
    +}
    +
    +export type PhpCallableArgs = PhpInput[]
    +export type PhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = (
    +  ...args: TArgs
    +) => TResult
    +export type PhpCallableScope = PhpInput
    +export type PhpCallableTuple<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = readonly [
    +  PhpCallableScope,
    +  string | PhpCallable<TArgs, TResult>,
    +]
    +export type PhpCallableDescriptor<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> =
    +  | string
    +  | PhpCallable<TArgs, TResult>
    +  | PhpCallableTuple<TArgs, TResult>
    +export type PhpComparatorDescriptor<T> = PhpCallableDescriptor<[T, T], NumericLike>
    +export type PhpKeyComparatorDescriptor = PhpCallableDescriptor<[string, string], NumericLike>
    +
    +export function isPhpNullish(value: PhpInput): value is PhpNullish {
    +  return typeof value === 'undefined' || value === null
    +}
    +
    +export function isPhpList<T = PhpInput>(value: PhpInput): value is PhpList<T> {
    +  return Array.isArray(value)
    +}
    +
    +export function isObjectLike(value: PhpInput): value is PhpArrayLike<PhpInput> {
    +  return typeof value === 'object' && value !== null
    +}
    +
    +export function isPhpAssocObject<T = PhpInput>(value: PhpInput): value is PhpAssoc<T> {
    +  return isObjectLike(value) && !isPhpList(value)
    +}
    +
    +export function isPhpScalar(value: PhpInput): value is PhpScalar {
    +  return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
    +}
    +
    +export function isPhpKey(value: PhpInput): value is PhpKey {
    +  return typeof value === 'string' || typeof value === 'number'
    +}
    +
    +export function isNumericLike(value: PhpInput): value is NumericLike {
    +  if (typeof value === 'number') {
    +    return Number.isFinite(value)
    +  }
    +
    +  if (typeof value === 'bigint') {
    +    return true
    +  }
    +
    +  if (typeof value !== 'string') {
    +    return false
    +  }
    +
    +  const trimmed = value.trim()
    +  if (trimmed === '') {
    +    return false
    +  }
    +
    +  return Number.isFinite(Number(trimmed))
    +}
    +
    +export function isPhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput>(
    +  value: PhpInput,
    +): value is PhpCallable<TArgs, TResult> {
    +  return typeof value === 'function'
    +}
    +
    +export function isPhpCallableDescriptor<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput>(
    +  value: PhpInput,
    +): value is PhpCallableDescriptor<TArgs, TResult> {
    +  if (typeof value === 'string') {
    +    return true
    +  }
    +
    +  if (isPhpCallable<TArgs, TResult>(value)) {
    +    return true
    +  }
    +
    +  if (!Array.isArray(value) || value.length < 2) {
    +    return false
    +  }
    +
    +  const callableDescriptor = value[1]
    +  return typeof callableDescriptor === 'string' || isPhpCallable<TArgs, TResult>(callableDescriptor)
    +}
    +
    +export function assertIsObjectLike(
    +  value: PhpInput,
    +  message = 'Expected object-like value',
    +): asserts value is PhpArrayLike<PhpInput> {
    +  if (!isObjectLike(value)) {
    +    throw new TypeError(message)
    +  }
    +}
    +
    +export function assertIsPhpAssocObject(
    +  value: PhpInput,
    +  message = 'Expected associative object value',
    +): asserts value is PhpAssoc<PhpInput> {
    +  if (!isPhpAssocObject(value)) {
    +    throw new TypeError(message)
    +  }
    +}
    +
    +export function assertIsPhpList(value: PhpInput, message = 'Expected list value'): asserts value is PhpList<PhpInput> {
    +  if (!isPhpList(value)) {
    +    throw new TypeError(message)
    +  }
    +}
    +
    +export function assertIsPhpKey(value: PhpInput, message = 'Expected key value'): asserts value is PhpKey {
    +  if (!isPhpKey(value)) {
    +    throw new TypeError(message)
    +  }
    +}
    +
    +export function assertIsNumericLike(
    +  value: PhpInput,
    +  message = 'Expected numeric-like value',
    +): asserts value is NumericLike {
    +  if (!isNumericLike(value)) {
    +    throw new TypeError(message)
    +  }
    +}
    +
    +export function assertIsPhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput>(
    +  value: PhpInput,
    +  message = 'Expected callable value',
    +): asserts value is PhpCallable<TArgs, TResult> {
    +  if (!isPhpCallable<TArgs, TResult>(value)) {
    +    throw new TypeError(message)
    +  }
    +}
    +
    +export function isPhpArrayObject<T = PhpInput>(value: PhpInput): value is PhpAssoc<T> {
    +  return isObjectLike(value)
    +}
    +
    +export function toPhpArrayObject<T = PhpInput>(value: PhpInput): PhpAssoc<T> {
    +  if (isPhpArrayObject<T>(value)) {
    +    return value
    +  }
    +
    +  return {}
    +}
    
  • src/_util/headerFormatter.ts+16 11 modified
    @@ -9,11 +9,7 @@ import path from 'node:path'
     import globby from 'globby'
     import { isValidHeaderKey } from './headerSchema.ts'
     
    -interface ParsedLine {
    -  isHeader: boolean
    -  key?: string
    -  value?: string
    -}
    +type ParsedLine = { isHeader: false } | { isHeader: true; key: string; value: string }
     
     interface FormatResult {
       formatted: string
    @@ -50,6 +46,9 @@ export function parseHeaderLine(line: string): ParsedLine {
       }
     
       const [, key, value] = match
    +  if (typeof key === 'undefined' || typeof value === 'undefined') {
    +    return { isHeader: false }
    +  }
       const keyLower = key.toLowerCase().trim()
     
       if (!isValidHeaderKey(keyLower)) {
    @@ -78,23 +77,29 @@ export function formatHeaders(content: string): FormatResult {
       const lines = content.split('\n')
     
       const parsedLines = lines.map((line) => parseHeaderLine(line))
    -  const headerKeys = parsedLines.filter((p) => p.isHeader && p.key).map((p) => p.key as string)
    +  const headerKeys = parsedLines
    +    .filter((p): p is Extract<ParsedLine, { isHeader: true }> => p.isHeader)
    +    .map((p) => p.key)
       const longestKeyLength = headerKeys.length > 0 ? Math.max(...headerKeys.map((k) => k.length)) : 0
     
       let changed = false
       const formattedLines: string[] = []
     
       for (let i = 0; i < lines.length; i++) {
    +    const line = lines[i]
    +    if (typeof line === 'undefined') {
    +      continue
    +    }
         const parsed = parsedLines[i]
     
    -    if (parsed.isHeader && parsed.key && parsed.value !== undefined) {
    +    if (parsed && parsed.isHeader) {
           const formatted = formatHeaderLine(parsed.key, parsed.value, longestKeyLength)
    -      if (formatted !== lines[i]) {
    +      if (formatted !== line) {
             changed = true
           }
           formattedLines.push(formatted)
         } else {
    -      formattedLines.push(lines[i])
    +      formattedLines.push(line)
         }
       }
     
    @@ -134,7 +139,7 @@ export function formatFile(filepath: string): FormatFileResult {
      * Check all function files for proper header formatting
      */
     export function checkAll(srcDir: string): CheckAllResult {
    -  const pattern = [path.join(srcDir, '**/*.js'), '!**/index.js', '!**/_util/**']
    +  const pattern = [path.join(srcDir, '**/*.{js,ts}'), '!**/index.{js,ts}', '!**/_util/**', '!**/*.vitest.ts']
       const files = globby.sync(pattern)
       const badFiles: string[] = []
     
    @@ -155,7 +160,7 @@ export function checkAll(srcDir: string): CheckAllResult {
      * Format all function files
      */
     export function formatAll(srcDir: string): FormatAllResult {
    -  const pattern = [path.join(srcDir, '**/*.js'), '!**/index.js', '!**/_util/**']
    +  const pattern = [path.join(srcDir, '**/*.{js,ts}'), '!**/index.{js,ts}', '!**/_util/**', '!**/*.vitest.ts']
       const files = globby.sync(pattern)
       const formattedFiles: string[] = []
     
    
  • src/_util/util.ts+1980 314 modified
    @@ -1,12 +1,13 @@
    +import { spawnSync } from 'node:child_process'
     import fs from 'node:fs'
     import path from 'node:path'
     import Debug from 'debug'
    -import esprima from 'esprima'
     import globby from 'globby'
     import indentStringModule from 'indent-string'
     import YAML from 'js-yaml'
     import _ from 'lodash'
     import pMap from 'p-map'
    +import ts from 'typescript'
     import { isValidHeaderKey, validateHeaderKeys } from './headerSchema.ts'
     
     const debug = Debug('locutus:utils')
    @@ -40,13 +41,69 @@ interface ParsedParams {
       codeDependencies?: string[] // Extracted require() paths
     }
     
    +interface StandaloneDependencyOptions {
    +  includeTypeOnlyImports?: boolean
    +}
    +
    +interface WebsiteJsOptions {
    +  preserveBlankLines?: boolean
    +}
    +
    +interface WebsiteJsFormatOptions {
    +  restorePreservedBlankLines?: boolean
    +}
    +
    +type StandaloneMode = 'js' | 'ts'
    +
    +interface StandaloneImportSpec {
    +  depKey: string
    +  localName: string
    +  importedName: string
    +  isTypeOnly: boolean
    +}
    +
    +interface StandaloneStatementInfo {
    +  declaredNames: Set<string>
    +  runtimeTopLevelDeps: Set<string>
    +  allTopLevelDeps: Set<string>
    +  runtimeImportLocals: Set<string>
    +  typeImportLocals: Set<string>
    +}
    +
    +interface StandaloneModuleInfo {
    +  moduleKey: string
    +  modulePath: string
    +  params: ParsedParams
    +  sourceFile: ts.SourceFile
    +  hasExternalImports: boolean
    +  statements: ts.Statement[]
    +  statementInfo: StandaloneStatementInfo[]
    +  declarationsByName: Map<string, Set<number>>
    +  exportToLocalName: Map<string, string>
    +  importSpecsByLocalName: Map<string, StandaloneImportSpec[]>
    +}
    +
    +interface StandaloneModuleSelection {
    +  includedStatementIndexes: Set<number>
    +  depRuntimeNames: Map<string, Set<string>>
    +  depTypeNames: Map<string, Set<string>>
    +  runtimeAliases: Set<string>
    +  wrapperAliases: Set<string>
    +  wrapperRenameByStatementIndex: Map<number, string>
    +  typeAliases: Set<string>
    +}
    +
    +type UnknownMap = { [key: string]: unknown }
    +
     type Callback<T = void> = (err: Error | null, result?: T) => void
     
    +const WEBSITE_BLANK_LINE_MARKER_COMMENT = '/*__LOCUTUS_PRESERVE_BLANK_LINE__*/'
    +
     class Util {
       __src: string
       __root: string
       __test: string
    -  globals: Record<string, unknown>
    +  globals: UnknownMap
       pattern: string[]
       concurrency: number
       authorKeys: string[]
    @@ -65,7 +122,14 @@ class Util {
     
         this.globals = {}
     
    -    this.pattern = [this.__src + '/**/**/*.js', '!**/index.js', '!**/_util/**', '!**/*.mocha.js']
    +    this.pattern = [
    +      this.__src + '/**/**/*.{js,ts}',
    +      '!**/index.js',
    +      '!**/index.ts',
    +      '!**/_util/**',
    +      '!**/*.mocha.js',
    +      '!**/*.vitest.ts',
    +    ]
         this.concurrency = 8
         this.authorKeys = [
           'original by',
    @@ -80,18 +144,18 @@ class Util {
         this.langDefaults = {
           c: {
             order: 1,
    -        function_title_template: "[language]'s [category].[function] in JavaScript",
    +        function_title_template: "[language]'s [category].[function] in TypeScript",
             human: 'C',
             packageType: 'header file',
             inspiration_urls: [
               '<a href="https://en.cppreference.com/w/c/numeric/math">the C math.h documentation</a>',
               '<a href="https://sourceware.org/git/?p=glibc.git;a=tree;f=math;hb=HEAD">the C math.h source</a>',
             ],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://en.cppreference.com/w/c/numeric/[category]/[function]">[language]'s [function] found in the [category].h header file</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://en.cppreference.com/w/c/numeric/[category]/[function]">[language]'s [function] found in the [category].h header file</a> looks like.`,
           },
           golang: {
             order: 2,
    -        function_title_template: "[language]'s [category].[function] in JavaScript",
    +        function_title_template: "[language]'s [category].[function] in TypeScript",
             human: 'Go',
             packageType: 'package',
             inspiration_urls: [
    @@ -100,37 +164,37 @@ class Util {
               '<a href="https://golang.org/src/strings/example_test.go">Go strings examples source</a>',
               '<a href="https://gophersjs.com">GopherJS</a>',
             ],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://golang.org/pkg/[category]/#[function]">[language]'s [category].[function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://golang.org/pkg/[category]/#[function]">[language]'s [category].[function]</a> looks like.`,
           },
           python: {
             order: 3,
    -        function_title_template: "[language]'s [category].[function] in JavaScript",
    +        function_title_template: "[language]'s [category].[function] in TypeScript",
             human: 'Python',
             packageType: 'module',
             inspiration_urls: [
               '<a href="https://docs.python.org/3/library/string.html">the Python 3 standard library string page</a>',
             ],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://docs.python.org/3/library/[category].html#[category].[function]">[language]'s [category].[function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://docs.python.org/3/library/[category].html#[category].[function]">[language]'s [category].[function]</a> looks like.`,
           },
           ruby: {
             order: 4,
    -        function_title_template: "[language]'s [category].[function] in JavaScript",
    +        function_title_template: "[language]'s [category].[function] in TypeScript",
             human: 'Ruby',
             packageType: 'module',
             inspiration_urls: ['<a href="https://ruby-doc.org/core-2.2.2/Math.html">the Ruby core documentation</a>'],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://ruby-doc.org/core-2.2.2/[category].html#method-c-[function]">[language]'s [category].[function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://ruby-doc.org/core-2.2.2/[category].html#method-c-[function]">[language]'s [category].[function]</a> looks like.`,
           },
           php: {
             order: 5,
    -        function_title_template: "[language]'s [function] in JavaScript",
    +        function_title_template: "[language]'s [function] in TypeScript",
             human: 'PHP',
             packageType: 'extension',
             inspiration_urls: [
               '<a href="https://php.net/manual/en/book.strings.php">the PHP string documentation</a>',
               '<a href="https://github.com/php/php-src/blob/master/ext/standard/string.c#L5338">the PHP string source</a>',
               '<a href="https://github.com/php/php-src/blob/master/ext/standard/tests/strings/str_pad_variation1.phpt">a PHP str_pad test</a>',
             ],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://php.net/manual/en/function.[functiondashed].php">[language]'s [function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://php.net/manual/en/function.[functiondashed].php">[language]'s [function]</a> looks like.`,
             alias: [
               '/categories/',
               '/categories/array/',
    @@ -162,61 +226,61 @@ class Util {
           },
           perl: {
             order: 6,
    -        function_title_template: "[language]'s [category]::[function] in JavaScript",
    +        function_title_template: "[language]'s [category]::[function] in TypeScript",
             human: 'Perl',
             packageType: 'module',
             inspiration_urls: ['<a href="https://perldoc.perl.org/">the Perl documentation</a>'],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://perldoc.perl.org/[category]">[language]'s [category]::[function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://perldoc.perl.org/[category]">[language]'s [category]::[function]</a> looks like.`,
           },
           lua: {
             order: 7,
    -        function_title_template: "[language]'s [category].[function] in JavaScript",
    +        function_title_template: "[language]'s [category].[function] in TypeScript",
             human: 'Lua',
             packageType: 'library',
             inspiration_urls: ['<a href="https://www.lua.org/manual/5.4/">the Lua 5.4 manual</a>'],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://www.lua.org/manual/5.4/manual.html#pdf-[category].[function]">[language]'s [category].[function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://www.lua.org/manual/5.4/manual.html#pdf-[category].[function]">[language]'s [category].[function]</a> looks like.`,
           },
           r: {
             order: 8,
    -        function_title_template: "[language]'s [function] in JavaScript",
    +        function_title_template: "[language]'s [function] in TypeScript",
             human: 'R',
             packageType: 'function',
             inspiration_urls: [
               '<a href="https://stat.ethz.ch/R-manual/R-devel/library/base/html/00Index.html">the R base documentation</a>',
             ],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://stat.ethz.ch/R-manual/R-devel/library/[category]/html/[function].html">[language]'s [function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://stat.ethz.ch/R-manual/R-devel/library/[category]/html/[function].html">[language]'s [function]</a> looks like.`,
           },
           julia: {
             order: 9,
    -        function_title_template: "[language]'s [function] in JavaScript",
    +        function_title_template: "[language]'s [function] in TypeScript",
             human: 'Julia',
             packageType: 'function',
             inspiration_urls: ['<a href="https://docs.julialang.org/en/v1/">the Julia documentation</a>'],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://docs.julialang.org/en/v1/base/math/#Base.[function]">[language]'s [function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://docs.julialang.org/en/v1/base/math/#Base.[function]">[language]'s [function]</a> looks like.`,
           },
           elixir: {
             order: 10,
    -        function_title_template: "[language]'s [category].[function] in JavaScript",
    +        function_title_template: "[language]'s [category].[function] in TypeScript",
             human: 'Elixir',
             packageType: 'module',
             inspiration_urls: ['<a href="https://hexdocs.pm/elixir/">the Elixir documentation</a>'],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://hexdocs.pm/elixir/[category].html#[function]/1">[language]'s [category].[function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://hexdocs.pm/elixir/[category].html#[function]/1">[language]'s [category].[function]</a> looks like.`,
           },
           clojure: {
             order: 11,
    -        function_title_template: "[language]'s [category]/[function] in JavaScript",
    +        function_title_template: "[language]'s [category]/[function] in TypeScript",
             human: 'Clojure',
             packageType: 'function',
             inspiration_urls: ['<a href="https://clojuredocs.org/">ClojureDocs</a>'],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://clojuredocs.org/clojure.core/[function]">[language]'s [category]/[function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://clojuredocs.org/clojure.core/[function]">[language]'s [category]/[function]</a> looks like.`,
           },
           awk: {
             order: 12,
    -        function_title_template: "[language]'s [function] in JavaScript",
    +        function_title_template: "[language]'s [function] in TypeScript",
             human: 'AWK',
             packageType: 'function',
             inspiration_urls: ['<a href="https://www.gnu.org/software/gawk/manual/gawk.html">the GNU AWK manual</a>'],
    -        function_description_template: `Here's what our current JavaScript equivalent to <a href="https://www.gnu.org/software/gawk/manual/gawk.html#String-Functions">[language]'s [function]</a> looks like.`,
    +        function_description_template: `Here's what our current TypeScript equivalent to <a href="https://www.gnu.org/software/gawk/manual/gawk.html#String-Functions">[language]'s [function]</a> looks like.`,
           },
         }
     
    @@ -243,8 +307,12 @@ class Util {
           // Write all buffered index files
           let filesWritten = 0
           for (const indexHtml in this._injectwebBuffer) {
    +        const html = this._injectwebBuffer[indexHtml]
    +        if (typeof html === 'undefined') {
    +          continue
    +        }
             debug('writing: ' + indexHtml)
    -        fs.writeFileSync(indexHtml, this._injectwebBuffer[indexHtml], 'utf-8')
    +        fs.writeFileSync(indexHtml, html, 'utf-8')
             filesWritten++
           }
     
    @@ -286,6 +354,9 @@ class Util {
     
           for (const indexJs in this._reindexBuffer) {
             const requires = this._reindexBuffer[indexJs]
    +        if (!requires) {
    +          continue
    +        }
             requires.sort()
             debug('writing: ' + indexJs)
             fs.writeFileSync(indexJs, requires.join('\n') + '\n', 'utf-8')
    @@ -330,20 +401,53 @@ class Util {
       _reindexOne(params: ParsedParams): Promise<void> {
         const fullpath = this.__src + '/' + params.filepath
         const dir = path.dirname(fullpath)
    -    const basefile = path.basename(fullpath, '.js')
    -    const indexJs = dir + '/index.js'
    +    const ext = path.extname(fullpath)
    +    const basefile = path.basename(fullpath, ext)
    +    const indexTs = dir + '/index.ts'
     
    -    let module = basefile
    -    if (basefile === 'Index2') {
    -      module = 'Index'
    +    if (!this._reindexBuffer[indexTs]) {
    +      this._reindexBuffer[indexTs] = []
         }
     
    -    if (!this._reindexBuffer[indexJs]) {
    -      this._reindexBuffer[indexJs] = []
    +    const scriptKind = params.filepath.endsWith('.ts') ? ts.ScriptKind.TS : ts.ScriptKind.JS
    +    const sourceFile = ts.createSourceFile(params.filepath, params.code, ts.ScriptTarget.ES2022, true, scriptKind)
    +    const exportNames = new Set<string>()
    +
    +    ts.forEachChild(sourceFile, (node) => {
    +      if (ts.isFunctionDeclaration(node) && node.name) {
    +        const modifiers = ts.getModifiers(node)
    +        const hasExport = modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
    +        if (hasExport) {
    +          exportNames.add(node.name.text)
    +        }
    +        return
    +      }
    +
    +      if (ts.isVariableStatement(node)) {
    +        const modifiers = ts.getModifiers(node)
    +        const hasExport = modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
    +        if (!hasExport) {
    +          return
    +        }
    +
    +        for (const declaration of node.declarationList.declarations) {
    +          if (ts.isIdentifier(declaration.name)) {
    +            exportNames.add(declaration.name.text)
    +          }
    +        }
    +      }
    +    })
    +
    +    if (exportNames.size === 0) {
    +      exportNames.add(params.func_name)
         }
     
    -    const line = 'module.exports.' + module + " = require('./" + basefile + "')"
    -    this._reindexBuffer[indexJs].push(line)
    +    for (const exportName of exportNames) {
    +      const line = `export { ${exportName} } from './${basefile}.ts'`
    +      if (!this._reindexBuffer[indexTs]?.includes(line)) {
    +        this._reindexBuffer[indexTs]?.push(line)
    +      }
    +    }
         return Promise.resolve()
       }
     
    @@ -361,14 +465,20 @@ class Util {
         const catPath = langPath + '/' + params.category
         const catIndexPath = catPath + '/' + 'index.html'
         const funcPath = catPath + '/' + params.func_name + '.html'
    +    const langDefaults = this.langDefaults[params.language]
    +    if (!langDefaults) {
    +      throw new Error(`Unknown language defaults for: ${params.language}`)
    +    }
    +
    +    const targetLangLabel = params.filepath.endsWith('.ts') ? 'TypeScript' : 'JavaScript'
     
         if (!this._injectwebBuffer[langIndexPath]) {
           let langTitle = ''
    -      langTitle += this.langDefaults[params.language].human + ' '
    -      langTitle += this.langDefaults[params.language].packageType + 's '
    -      langTitle += ' in JavaScript'
    +      langTitle += langDefaults.human + ' '
    +      langTitle += langDefaults.packageType + 's '
    +      langTitle += ` in ${targetLangLabel}`
     
    -      const langData = Object.assign({}, this.langDefaults[params.language], {
    +      const langData = Object.assign({}, langDefaults, {
             warning: 'This file is auto generated by `yarn web:inject`, do not edit by hand',
             type: 'language',
             layout: 'language',
    @@ -380,10 +490,10 @@ class Util {
     
         if (!this._injectwebBuffer[catIndexPath]) {
           let catTitle = ''
    -      catTitle += this.langDefaults[params.language].human + "'s "
    +      catTitle += langDefaults.human + "'s "
           catTitle += params.category + ' '
    -      catTitle += this.langDefaults[params.language].packageType + ' '
    -      catTitle += ' in JavaScript'
    +      catTitle += langDefaults.packageType + ' '
    +      catTitle += ` in ${targetLangLabel}`
     
           const catData = {
             warning: 'This file is auto generated by `yarn web:inject`, do not edit by hand',
    @@ -396,29 +506,32 @@ class Util {
           this._injectwebBuffer[catIndexPath] = '---' + '\n' + YAML.dump(catData).trim() + '\n' + '---' + '\n'
         }
     
    -    const functionTitle = this.langDefaults[params.language].function_title_template
    -      .replace(/\[language]/g, this.langDefaults[params.language].human)
    +    const functionTitle = langDefaults.function_title_template
    +      .replace(/\[language]/g, langDefaults.human)
           .replace(/\[category]/g, params.category)
           .replace(/\[function]/g, params.func_name)
           .replace(/\[functiondashed]/g, params.func_name.replace(/_/g, '-'))
     
    -    const functionDescription = this.langDefaults[params.language].function_description_template
    -      .replace(/\[language]/g, this.langDefaults[params.language].human)
    +    const functionDescription = langDefaults.function_description_template
    +      .replace(/\[language]/g, langDefaults.human)
           .replace(/\[category]/g, params.category)
           .replace(/\[function]/g, params.func_name)
           .replace(/\[functiondashed]/g, params.func_name.replace(/_/g, '-'))
     
         // Extract parity verified values (e.g., "PHP 8.3", "Python 3.12")
         const parityVerified = (params.headKeys['parity verified'] || []).map((lines) => lines.join('\n'))
     
    -    const funcData: Record<string, unknown> = {
    +    const isTS = params.filepath.endsWith('.ts')
    +
    +    const funcData: UnknownMap = {
           warning: 'This file is auto generated by `yarn web:inject`, do not edit by hand',
           examples: (params.headKeys.example || []).map((lines) => lines.join('\n')),
           returns: (params.headKeys.returns || []).map((lines) => lines.join('\n')),
           dependencies: params.codeDependencies || [],
           authors: authors || {},
           notes: (params.headKeys.note || []).map((lines) => lines.join('\n')),
           parityVerified: parityVerified.length > 0 ? parityVerified : null,
    +      isTypescript: isTS,
           type: 'function',
           layout: 'function',
           title: functionTitle,
    @@ -439,339 +552,1894 @@ class Util {
         }
     
         let buf = '---' + '\n' + YAML.dump(funcData).trim() + '\n' + '---' + '\n'
    -    buf += `{% codeblock lang:javascript %}${params.code}{% endcodeblock %}`
    +
    +    if (isTS) {
    +      const hasStandaloneDependencies = (params.codeDependencies || []).length > 0
    +      const jsCode = this._formatWebsiteJavascript(
    +        this._toWebsiteJs(params.code, { preserveBlankLines: true }),
    +        params.filepath.replace(/\.ts$/, '.js'),
    +        { restorePreservedBlankLines: true },
    +      )
    +      const standaloneCode = hasStandaloneDependencies ? await this._buildStandaloneJs(params) : ''
    +      const standaloneTsCode = hasStandaloneDependencies ? await this._buildStandaloneTs(params) : null
    +
    +      buf += `<div class="code-tab-panel" data-lang="ts">\n{% codeblock lang:typescript %}${params.code}{% endcodeblock %}\n</div>\n`
    +      buf += `<div class="code-tab-panel" data-lang="js" style="display:none">\n{% codeblock lang:javascript %}${jsCode}{% endcodeblock %}\n</div>\n`
    +      if (hasStandaloneDependencies && standaloneTsCode) {
    +        buf += `<div class="code-tab-panel" data-lang="standalone-ts" style="display:none">\n{% codeblock lang:typescript %}${standaloneTsCode}{% endcodeblock %}\n</div>\n`
    +      }
    +      if (hasStandaloneDependencies && standaloneCode.trim().length > 0) {
    +        buf += `<div class="code-tab-panel" data-lang="standalone-js" style="display:none">\n{% codeblock lang:javascript %}${standaloneCode}{% endcodeblock %}\n</div>`
    +      }
    +    } else {
    +      buf += `{% codeblock lang:javascript %}${params.code}{% endcodeblock %}`
    +    }
     
         await fs.promises.mkdir(path.dirname(funcPath), { recursive: true })
         await fs.promises.writeFile(funcPath, buf, 'utf-8')
       }
     
       _addRequire(name: string, relativeSrcForTest: string): string {
    -    return ['const ', name, " = require('", relativeSrcForTest, "')"].join('')
    +    const isTs = relativeSrcForTest.endsWith('.ts')
    +    const suffix = isTs ? '.' + name : ''
    +    return ['const ', name, " = require('", relativeSrcForTest, "')", suffix].join('')
       }
     
    -  async _writetestOne(params: ParsedParams): Promise<void> {
    -    if (!params.func_name) {
    -      throw new Error('No func_name in ' + JSON.stringify(params))
    -    }
    -    if (!params.headKeys) {
    -      throw new Error('No headKeys in ' + params.func_name)
    +  _addRequireExport(localName: string, exportName: string, relativeSrcForTest: string): string {
    +    const isTs = relativeSrcForTest.endsWith('.ts')
    +    if (isTs) {
    +      return ['const ', localName, " = require('", relativeSrcForTest, "').", exportName].join('')
         }
    -    if (!params.headKeys.example) {
    -      throw new Error('No example in ' + params.func_name)
    +    return ['const ', localName, " = require('", relativeSrcForTest, "')"].join('')
    +  }
    +
    +  _resolveSourceExt(subpath: string): string {
    +    if (fs.existsSync(path.join(this.__src, subpath + '.ts'))) {
    +      return subpath + '.ts'
         }
    +    return subpath + '.js'
    +  }
     
    -    const basename = path.basename(params.filepath, '.js') + '.vitest.ts'
    -    const subdir = path.dirname(params.filepath)
    -    const testpath = this.__test + '/generated/' + subdir + '/' + basename
    -    const testdir = path.dirname(testpath)
    -    const relativeSrcForTestDir = path.relative(testdir, this.__src)
    -    const relativeTestFileForRoot = path.relative(this.__root, testpath)
    +  _markWebsiteBlankLines(code: string): string {
    +    const lines = code.split('\n')
    +    let inBlockComment = false
    +
    +    const updateBlockCommentState = (line: string): void => {
    +      let cursor = 0
    +      while (cursor < line.length) {
    +        if (!inBlockComment) {
    +          const lineCommentIndex = line.indexOf('//', cursor)
    +          const blockStartIndex = line.indexOf('/*', cursor)
    +          if (blockStartIndex === -1) {
    +            return
    +          }
    +          if (lineCommentIndex !== -1 && lineCommentIndex < blockStartIndex) {
    +            return
    +          }
    +          const blockEndIndex = line.indexOf('*/', blockStartIndex + 2)
    +          if (blockEndIndex === -1) {
    +            inBlockComment = true
    +            return
    +          }
    +          cursor = blockEndIndex + 2
    +          continue
    +        }
     
    -    let testProps = ''
    -    if (params.headKeys.test) {
    -      testProps = params.headKeys.test[0][0]
    +        const blockEndIndex = line.indexOf('*/', cursor)
    +        if (blockEndIndex === -1) {
    +          return
    +        }
    +        inBlockComment = false
    +        cursor = blockEndIndex + 2
    +      }
         }
     
    -    let describeSkip = ''
    -    if (this.allowSkip && testProps.indexOf('skip-all') !== -1) {
    -      describeSkip = '.skip'
    -    }
    +    return lines
    +      .map((line) => {
    +        if (line.trim().length === 0 && !inBlockComment) {
    +          return WEBSITE_BLANK_LINE_MARKER_COMMENT
    +        }
    +        updateBlockCommentState(line)
    +        return line
    +      })
    +      .join('\n')
    +  }
     
    -    const codez: string[] = []
    +  _restoreWebsiteBlankLines(code: string): string {
    +    return code.replace(/^\s*\/\*__LOCUTUS_PRESERVE_BLANK_LINE__\*\/;?\s*$/gm, '')
    +  }
     
    -    codez.push('// warning: This file is auto generated by `yarn build:tests`')
    -    codez.push('// Do not edit by hand!')
    -    codez.push('')
    -    codez.push("import { describe, it, expect } from 'vitest'")
    -    codez.push('')
    +  _toWebsiteJs(code: string, options: WebsiteJsOptions = {}): string {
    +    // website/source snippets are generated snapshots; refresh with `yarn injectweb` after signature changes.
    +    const sourceCode = options.preserveBlankLines ? this._markWebsiteBlankLines(code) : code
    +    const jsResult = ts.transpileModule(sourceCode, {
    +      compilerOptions: {
    +        target: ts.ScriptTarget.ES2022,
    +        module: ts.ModuleKind.ESNext,
    +        removeComments: false,
    +        newLine: ts.NewLineKind.LineFeed,
    +      },
    +    })
     
    -    for (const global in this.globals) {
    -      codez.push('const ' + global + ' = ' + this.globals[global])
    -    }
    +    // Halve indentation (tsc emits 4 spaces, we use 2) and trim trailing whitespace
    +    return jsResult.outputText
    +      .split('\n')
    +      .map((line) => {
    +        const match = line.match(/^( +)/)
    +        if (!match) {
    +          return line
    +        }
    +        const prefix = match[1]
    +        if (!prefix) {
    +          return line
    +        }
    +        const spaces = prefix.length
    +        return ' '.repeat(Math.floor(spaces / 2)) + line.slice(spaces)
    +      })
    +      .join('\n')
    +      .trimEnd()
    +  }
     
    -    codez.push("process.env.TZ = 'UTC'")
    +  _resolveBiomeBinPath(): string | null {
    +    const biomeBinName = process.platform === 'win32' ? 'biome.cmd' : 'biome'
    +    const localBiomeBin = path.join(this.__root, 'node_modules', '.bin', biomeBinName)
    +    if (fs.existsSync(localBiomeBin)) {
    +      return localBiomeBin
    +    }
    +    return null
    +  }
     
    -    if (params.language === 'php') {
    -      // Add PHP helpers, but skip if we're testing that helper itself
    -      if (params.func_name !== 'ini_set') {
    -        codez.push(this._addRequire('ini_set', relativeSrcForTestDir + '/' + 'php/info/ini_set'))
    -      }
    -      if (params.func_name !== 'ini_get') {
    -        codez.push(this._addRequire('ini_get', relativeSrcForTestDir + '/' + 'php/info/ini_get'))
    -      }
    -      if (params.func_name === 'localeconv') {
    -        codez.push(this._addRequire('setlocale', relativeSrcForTestDir + '/' + 'php/strings/setlocale'))
    -      }
    -      if (params.func_name === 'i18n_loc_get_default') {
    -        codez.push(
    -          this._addRequire('i18n_loc_set_default', relativeSrcForTestDir + '/' + 'php/i18n/i18n_loc_set_default'),
    -        )
    -      }
    +  _formatWebsiteJavascript(code: string, sourcePath: string, options: WebsiteJsFormatOptions = {}): string {
    +    const finalize = (text: string): string => {
    +      const formatted = options.restorePreservedBlankLines ? this._restoreWebsiteBlankLines(text) : text
    +      return formatted.trimEnd()
         }
     
    -    codez.push(this._addRequire(params.func_name, relativeSrcForTestDir + '/' + params.filepath))
    -    codez.push('')
    +    const input = code.trimEnd()
    +    if (!input) {
    +      return ''
    +    }
     
    -    codez.push(
    -      [
    -        'describe',
    -        describeSkip,
    -        "('src/",
    -        params.filepath,
    -        ' (tested in ',
    -        relativeTestFileForRoot,
    -        ")', function () {",
    -      ].join(''),
    -    )
    +    const biomeBin = this._resolveBiomeBinPath()
    +    if (!biomeBin) {
    +      return finalize(input)
    +    }
     
    -    for (const i in params.headKeys.example) {
    -      if (!params.headKeys.returns[i] || !params.headKeys.returns[i].length) {
    -        throw new Error(`There is no return for example ${i} in ${params.filepath}`)
    -      }
    +    const result = spawnSync(biomeBin, ['format', '--stdin-file-path', sourcePath], {
    +      encoding: 'utf8',
    +      input,
    +      maxBuffer: 16 * 1024 * 1024,
    +    })
     
    -      const humanIndex = parseInt(i, 10) + 1
    -      let itSkip = ''
    -      if (this.allowSkip && testProps.indexOf('skip-' + humanIndex) !== -1) {
    -        itSkip = '.skip'
    +    if (result.error || result.status !== 0 || typeof result.stdout !== 'string') {
    +      const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : ''
    +      if (stderr) {
    +        debug(`biome format failed for ${sourcePath}: ${stderr}`)
           }
    +      return finalize(input)
    +    }
     
    -      codez.push(['  it', itSkip, "('should pass example ", humanIndex, "', function () {"].join(''))
    +    const output = result.stdout.trimEnd()
    +    return finalize(output || input)
    +  }
     
    -      const body: string[] = []
    -      const testExpected = params.headKeys.returns[i].join('\n')
    +  _toCommonJsRuntimeCode(code: string, options: { sourcePath: string }): string {
    +    return ts
    +      .transpileModule(code, {
    +        compilerOptions: {
    +          target: ts.ScriptTarget.ES2022,
    +          module: ts.ModuleKind.CommonJS,
    +          esModuleInterop: true,
    +          allowSyntheticDefaultImports: true,
    +          removeComments: false,
    +          newLine: ts.NewLineKind.LineFeed,
    +        },
    +        fileName: options.sourcePath,
    +      })
    +      .outputText.trimEnd()
    +  }
     
    -      body.push('const expected = ' + testExpected)
    +  _extractRelativeImportAliases(sourceFile: ts.SourceFile): string[] {
    +    const aliases = new Set<string>()
     
    -      for (const j in params.headKeys.example[i]) {
    -        if (parseInt(j, 10) === params.headKeys.example[i].length - 1) {
    -          body.push('const result = ' + params.headKeys.example[i][j].replace('var $result = ', ''))
    -        } else {
    -          body.push(params.headKeys.example[i][j])
    -        }
    +    const addAlias = (localName: string, importedName: string): void => {
    +      if (localName === importedName) {
    +        return
           }
    -
    -      body.push('expect(result).toEqual(expected)')
    -
    -      codez.push(indentString(body.join('\n'), ' ', 4))
    -      codez.push('  })')
    +      aliases.add(`const ${localName} = ${importedName};`)
         }
     
    -    codez.push('})')
    -    codez.push('')
    +    ts.forEachChild(sourceFile, (node) => {
    +      if (!ts.isImportDeclaration(node) || !ts.isStringLiteral(node.moduleSpecifier)) {
    +        return
    +      }
     
    -    const code = codez.join('\n')
    +      const importPath = node.moduleSpecifier.text
    +      if (!importPath.startsWith('./') && !importPath.startsWith('../')) {
    +        return
    +      }
     
    -    await fs.promises.mkdir(testdir, { recursive: true })
    -    debug('writing: ' + testpath)
    -    await fs.promises.writeFile(testpath, code, 'utf-8')
    -  }
    +      const importClause = node.importClause
    +      if (!importClause || importClause.isTypeOnly) {
    +        return
    +      }
     
    -  async _opener(
    -    fileOrName: string,
    -    requesterParams: Partial<ParsedParams>,
    -  ): Promise<{ fullpath: string; code: string } | null> {
    -    let pattern: string
    +      if (importClause.namedBindings && ts.isNamedImports(importClause.namedBindings)) {
    +        for (const importElement of importClause.namedBindings.elements) {
    +          if (importElement.isTypeOnly) {
    +            continue
    +          }
    +          const localName = importElement.name.text
    +          const importedName = importElement.propertyName?.text || localName
    +          addAlias(localName, importedName)
    +        }
    +      }
    +    })
     
    -    const language = requesterParams.language || '*'
    +    return Array.from(aliases)
    +  }
     
    -    // Check for absolute path first (before checking for dots in basename)
    -    if (fileOrName.startsWith('/')) {
    -      pattern = fileOrName
    -    } else if (path.basename(fileOrName, '.js').indexOf('.') !== -1) {
    -      pattern = this.__src + '/' + language + '/' + fileOrName.replace(/\./g, '/') + '.js'
    -    } else if (fileOrName.indexOf('/') === -1) {
    -      pattern = this.__src + '/' + language + '/*/' + fileOrName + '.js'
    -    } else {
    -      pattern = this.__src + '/' + fileOrName
    -    }
    +  _stripLocutusModuleSyntax(jsCode: string): string {
    +    return jsCode
    +      .replace(/^\s*import[\s\S]*?from\s+['"](?:\.\.?\/)[^'"]+['"];\s*$/gm, '')
    +      .replace(/^\s*export\s+\{[^}]+\};?\s*$/gm, '')
    +      .replace(
    +        /^(\s*)export\s+(?=(?:async\s+)?function\b|const\b|let\b|var\b|class\b|type\b|interface\b|enum\b)/gm,
    +        '$1',
    +      )
    +      .replace(/^(\s*)export\s+default\s+/gm, '$1')
    +      .trim()
    +  }
     
    -    pattern = pattern.replace('golang/strings/Index.js', 'golang/strings/Index2.js')
    -    debug('loading: ' + pattern)
    -    const files = globby.sync(pattern, {})
    +  async _collectStandaloneDependencyParams(
    +    params: ParsedParams,
    +    options: StandaloneDependencyOptions = {},
    +    seen = new Set<string>(),
    +    visiting = new Set<string>(),
    +  ): Promise<ParsedParams[]> {
    +    const ordered: ParsedParams[] = []
    +    const includeTypeOnlyImports = options.includeTypeOnlyImports === true
    +
    +    const nestedDepsFor = (moduleParams: ParsedParams): string[] => {
    +      const deps = [...(moduleParams.codeDependencies || [])]
    +      if (!includeTypeOnlyImports) {
    +        return deps
    +      }
     
    -    if (files.length !== 1) {
    -      const msg = `Found ${files.length} occurances of ${fileOrName} via pattern: ${pattern}`
    -      throw new Error(msg)
    +      const moduleIsTs = moduleParams.filepath.endsWith('.ts')
    +      const scriptKind = moduleIsTs ? ts.ScriptKind.TS : ts.ScriptKind.JS
    +      const sourceFile = ts.createSourceFile(
    +        moduleParams.filepath,
    +        moduleParams.code,
    +        ts.ScriptTarget.ES2022,
    +        true,
    +        scriptKind,
    +      )
    +      const importDeps = this._extractDependencies(sourceFile, moduleParams.filepath, {
    +        includeTypeOnlyImports: true,
    +      })
    +      for (const importDep of importDeps) {
    +        if (deps.indexOf(importDep) === -1) {
    +          deps.push(importDep)
    +        }
    +      }
    +      return deps
         }
     
    -    const filepath = files[0]
    -
    -    if (path.basename(filepath) === 'index.js') {
    -      return null
    +    const visit = async (depCodePath: string): Promise<void> => {
    +      const depKey = depCodePath.replace(/\.(js|ts)$/, '')
    +      if (seen.has(depKey) || visiting.has(depKey)) {
    +        return
    +      }
    +      visiting.add(depKey)
    +
    +      const depLoadPath =
    +        depCodePath.endsWith('.js') || depCodePath.endsWith('.ts') ? depCodePath : this._resolveSourceExt(depCodePath)
    +      const depParams = await this._load(depLoadPath, params)
    +      if (depParams) {
    +        for (const nestedDep of nestedDepsFor(depParams)) {
    +          await visit(nestedDep)
    +        }
    +        ordered.push(depParams)
    +        seen.add(depKey)
    +      }
    +      visiting.delete(depKey)
         }
     
    -    if (!filepath) {
    -      throw new Error('Could not find ' + pattern)
    +    for (const depCodePath of nestedDepsFor(params)) {
    +      await visit(depCodePath)
         }
     
    -    const code = await fs.promises.readFile(filepath, 'utf-8')
    -    return { fullpath: filepath, code }
    +    return ordered
       }
     
    -  async _load(fileOrName: string, requesterParams: Partial<ParsedParams>): Promise<ParsedParams | null> {
    -    const result = await this._opener(fileOrName, requesterParams)
    -    if (!result) {
    -      return null
    -    }
    -
    -    const filepath = path.relative(this.__src, result.fullpath)
    -    return this._parse(filepath, result.code)
    +  async _buildStandaloneJs(params: ParsedParams): Promise<string> {
    +    return (await this._buildStandalone(params, 'js')) || ''
       }
     
    -  async _findDependencies(
    -    _fileOrName: string,
    -    requesterParams: ParsedParams,
    -    dependencies: Record<string, ParsedParams>,
    -  ): Promise<Record<string, ParsedParams>> {
    -    if (!requesterParams.headKeys['depends on'] || !requesterParams.headKeys['depends on'].length) {
    -      return {}
    -    }
    -
    -    for (const i in requesterParams.headKeys['depends on']) {
    -      const depCodePath = requesterParams.headKeys['depends on'][i][0]
    +  _buildStandaloneTs(params: ParsedParams): Promise<string | null> {
    +    return this._buildStandalone(params, 'ts')
    +  }
     
    -      const params = await this._load(depCodePath, requesterParams)
    -      if (params) {
    -        dependencies[depCodePath] = params
    -        await this._findDependencies(depCodePath, params, dependencies)
    +  async _buildStandalone(params: ParsedParams, mode: StandaloneMode): Promise<string | null> {
    +    const includeTypeOnlyImports = mode === 'ts'
    +    const dependencies = await this._collectStandaloneDependencyParams(params, {
    +      includeTypeOnlyImports,
    +    })
    +    const modules = [...dependencies, params]
    +    const moduleInfoByKey = new Map<string, StandaloneModuleInfo>()
    +    const moduleOrder: string[] = []
    +
    +    for (const moduleParams of modules) {
    +      const info = this._createStandaloneModuleInfo(moduleParams)
    +      if (mode === 'ts' && info.hasExternalImports) {
    +        return null
           }
    +      moduleInfoByKey.set(info.moduleKey, info)
    +      moduleOrder.push(info.moduleKey)
         }
     
    -    return dependencies
    -  }
    +    const rootKey = params.filepath.replace(/\.(js|ts)$/, '')
    +    const runtimeRequiredByModule = new Map<string, Set<string>>()
    +    const typeRequiredByModule = new Map<string, Set<string>>()
    +    this._addRequiredName(runtimeRequiredByModule, rootKey, params.func_name)
     
    -  /**
    -   * Extract require() calls from AST that reference other locutus functions
    -   * Returns array of resolved function paths (e.g., "php/strings/echo")
    -   */
    -  _extractRequires(ast: { body: unknown[] }, filepath: string): string[] {
    -    const requires: string[] = []
    -    const seen = new Set<string>()
    +    const selectionByModule = new Map<string, StandaloneModuleSelection>()
    +    let changed = true
    +    while (changed) {
    +      changed = false
     
    -    // Recursive AST walker to find require() calls
    -    const walk = (node: unknown): void => {
    -      if (!node || typeof node !== 'object') {
    -        return
    -      }
    +      for (const moduleKey of moduleOrder) {
    +        const info = moduleInfoByKey.get(moduleKey)
    +        if (!info) {
    +          continue
    +        }
     
    -      const n = node as Record<string, unknown>
    +        const runtimeRequired = runtimeRequiredByModule.get(moduleKey) || new Set<string>()
    +        const typeRequired = typeRequiredByModule.get(moduleKey) || new Set<string>()
    +        if (moduleKey !== rootKey && runtimeRequired.size === 0 && typeRequired.size === 0) {
    +          continue
    +        }
     
    -      // Check if this is a require() call
    -      if (
    -        n.type === 'CallExpression' &&
    -        n.callee &&
    -        (n.callee as { type?: string; name?: string }).type === 'Identifier' &&
    -        (n.callee as { name: string }).name === 'require' &&
    -        Array.isArray(n.arguments) &&
    -        n.arguments.length > 0
    -      ) {
    -        const arg = n.arguments[0] as { type?: string; value?: string }
    -        if (arg.type === 'Literal' && typeof arg.value === 'string') {
    -          const requirePath = arg.value
    -          // Only process relative requires that reference locutus functions
    -          if (requirePath.startsWith('../') || requirePath.startsWith('./')) {
    -            // Resolve the path relative to the current file
    -            const dir = path.dirname(filepath)
    -            const resolved = path.normalize(path.join(dir, requirePath))
    -            // Remove leading slashes and convert to locutus path format
    -            const locutusPath = resolved.replace(/^\/+/, '').replace(/\.js$/, '')
    -            if (!seen.has(locutusPath)) {
    -              seen.add(locutusPath)
    -              requires.push(locutusPath)
    +        const selection = this._selectStandaloneModuleSymbols(info, runtimeRequired, typeRequired, mode)
    +        selectionByModule.set(moduleKey, selection)
    +
    +        for (const [depKey, names] of selection.depRuntimeNames.entries()) {
    +          for (const name of names) {
    +            if (this._addRequiredName(runtimeRequiredByModule, depKey, name)) {
    +              changed = true
                 }
               }
             }
    -      }
     
    -      // Recursively walk child nodes
    -      for (const key of Object.keys(n)) {
    -        const child = n[key]
    -        if (Array.isArray(child)) {
    -          for (const item of child) {
    -            walk(item)
    +        for (const [depKey, names] of selection.depTypeNames.entries()) {
    +          for (const name of names) {
    +            if (this._addRequiredName(typeRequiredByModule, depKey, name)) {
    +              changed = true
    +            }
               }
    -        } else if (child && typeof child === 'object') {
    -          walk(child)
             }
           }
         }
     
    -    walk(ast)
    -    return requires
    -  }
    -
    -  async _parse(filepath: string, code: string): Promise<ParsedParams> {
    -    if (!code) {
    -      throw new Error('Unable to parse ' + filepath + '. Received no code')
    -    }
    -
    -    if (filepath.indexOf('/') === -1) {
    -      throw new Error("Parse only accepts relative filepaths. Received: '" + filepath + "'")
    -    }
    +    const chunks: string[] = []
    +    for (const moduleKey of moduleOrder) {
    +      const info = moduleInfoByKey.get(moduleKey)
    +      if (!info) {
    +        continue
    +      }
     
    -    const parts = filepath.split('/')
    -    const language = parts.shift() as string
    -    const codepath = parts.join('.')
    -    const name = parts.pop() as string
    -    const category = parts.join('.')
    +      const runtimeRequired = runtimeRequiredByModule.get(moduleKey) || new Set<string>()
    +      const typeRequired = typeRequiredByModule.get(moduleKey) || new Set<string>()
    +      if (moduleKey !== rootKey && runtimeRequired.size === 0 && typeRequired.size === 0) {
    +        continue
    +      }
     
    -    const ast = esprima.parseScript(code, { comment: true, loc: true, range: true })
    +      const selection =
    +        selectionByModule.get(moduleKey) ||
    +        this._selectStandaloneModuleSymbols(info, runtimeRequired, typeRequired, mode)
     
    -    const moduleExports = ast.body.filter((node: unknown) => {
    -      try {
    -        const n = node as {
    -          expression: {
    -            left: { object: { name: string }; property: { name: string } }
    -            right: { type: string; id: { type: string; name: string } }
    -          }
    -        }
    -        const leftArg = n.expression.left
    -        const rightArg = n.expression.right
    +      const selectedModuleCode = this._renderStandaloneModule(
    +        info,
    +        selection.includedStatementIndexes,
    +        selection.wrapperRenameByStatementIndex,
    +      )
    +      if (!selectedModuleCode.trim()) {
    +        continue
    +      }
     
    -        return (
    -          leftArg.object.name === 'module' &&
    -          leftArg.property.name === 'exports' &&
    -          rightArg.type === 'FunctionExpression' &&
    -          rightArg.id.type === 'Identifier' &&
    -          !!rightArg.id.name
    +      let renderedModuleCode = selectedModuleCode
    +      if (mode === 'js') {
    +        renderedModuleCode = this._stripLocutusModuleSyntax(
    +          this._toWebsiteJs(renderedModuleCode, { preserveBlankLines: true }),
             )
    -      } catch (_err) {
    +      } else {
    +        renderedModuleCode = this._stripLocutusModuleSyntax(renderedModuleCode)
    +      }
    +
    +      const aliasLines = [...selection.runtimeAliases, ...selection.typeAliases]
    +      const wrapperAliasLines = [...selection.wrapperAliases]
    +      const moduleLabel =
    +        moduleKey === rootKey ? 'target function module' : this._describeStandaloneDependencyRole(info.modulePath)
    +      let chunk = `// ${info.modulePath} (${moduleLabel})\n`
    +      if (aliasLines.length > 0) {
    +        chunk += aliasLines.join('\n') + '\n\n'
    +      }
    +      chunk += renderedModuleCode
    +      if (wrapperAliasLines.length > 0) {
    +        chunk += `\n\n${wrapperAliasLines.join('\n')}`
    +      }
    +      chunks.push(chunk.trim())
    +    }
    +
    +    let output = chunks.join('\n\n').trimEnd()
    +    if (mode === 'js') {
    +      output = this._collapseStandaloneJsPassthroughWrappers(output, params.func_name)
    +      return this._formatWebsiteJavascript(output, `${params.filepath}.standalone.js`, {
    +        restorePreservedBlankLines: true,
    +      })
    +    }
    +    return output
    +  }
    +
    +  _createStandaloneModuleInfo(moduleParams: ParsedParams): StandaloneModuleInfo {
    +    const moduleIsTs = moduleParams.filepath.endsWith('.ts')
    +    const scriptKind = moduleIsTs ? ts.ScriptKind.TS : ts.ScriptKind.JS
    +    const sourceFile = ts.createSourceFile(
    +      moduleParams.filepath,
    +      moduleParams.code,
    +      ts.ScriptTarget.ES2022,
    +      true,
    +      scriptKind,
    +    )
    +    const moduleKey = moduleParams.filepath.replace(/\.(js|ts)$/, '')
    +    const modulePath = moduleKey
    +    const statements = sourceFile.statements.slice()
    +    const declarationsByName = new Map<string, Set<number>>()
    +    const exportToLocalName = new Map<string, string>()
    +    const importSpecsByLocalName = new Map<string, StandaloneImportSpec[]>()
    +    const statementDeclaredNames: Array<Set<string>> = []
    +
    +    for (let index = 0; index < statements.length; index++) {
    +      const statement = statements[index]
    +      if (!statement) {
    +        continue
    +      }
    +
    +      const declaredNames = this._collectStatementDeclaredNames(statement)
    +      statementDeclaredNames.push(declaredNames)
    +
    +      for (const declaredName of declaredNames) {
    +        if (!declarationsByName.has(declaredName)) {
    +          declarationsByName.set(declaredName, new Set<number>())
    +        }
    +        declarationsByName.get(declaredName)?.add(index)
    +      }
    +
    +      const exportedNames = this._collectStatementExportedNames(statement, declaredNames)
    +      for (const [exportedName, localName] of exportedNames.entries()) {
    +        exportToLocalName.set(exportedName, localName)
    +      }
    +
    +      if (
    +        !ts.isImportDeclaration(statement) ||
    +        !statement.moduleSpecifier ||
    +        !ts.isStringLiteral(statement.moduleSpecifier)
    +      ) {
    +        continue
    +      }
    +
    +      const importPath = statement.moduleSpecifier.text
    +      if (!importPath.startsWith('./') && !importPath.startsWith('../')) {
    +        continue
    +      }
    +
    +      const depKey = path.normalize(path.join(path.dirname(moduleKey), importPath)).replace(/\.(js|ts)$/, '')
    +      const importClause = statement.importClause
    +      if (!importClause) {
    +        continue
    +      }
    +
    +      if (importClause.name) {
    +        this._addStandaloneImportSpec(importSpecsByLocalName, {
    +          depKey,
    +          localName: importClause.name.text,
    +          importedName: 'default',
    +          isTypeOnly: importClause.isTypeOnly,
    +        })
    +      }
    +
    +      if (importClause.namedBindings && ts.isNamedImports(importClause.namedBindings)) {
    +        for (const importElement of importClause.namedBindings.elements) {
    +          const localName = importElement.name.text
    +          const importedName = importElement.propertyName?.text || localName
    +          const isTypeOnly = importClause.isTypeOnly || importElement.isTypeOnly
    +          this._addStandaloneImportSpec(importSpecsByLocalName, {
    +            depKey,
    +            localName,
    +            importedName,
    +            isTypeOnly,
    +          })
    +        }
    +      }
    +    }
    +
    +    const topLevelNames = new Set<string>(declarationsByName.keys())
    +    const runtimeImportLocals = new Set<string>()
    +    const typeImportLocals = new Set<string>()
    +    for (const [localName, importSpecs] of importSpecsByLocalName.entries()) {
    +      const hasRuntimeImport = importSpecs.some((importSpec) => !importSpec.isTypeOnly)
    +      if (hasRuntimeImport) {
    +        runtimeImportLocals.add(localName)
    +      }
    +
    +      const hasTypeImport = importSpecs.some((importSpec) => importSpec.isTypeOnly)
    +      if (hasTypeImport) {
    +        typeImportLocals.add(localName)
    +      }
    +    }
    +
    +    const statementInfo: StandaloneStatementInfo[] = []
    +    for (let index = 0; index < statements.length; index++) {
    +      const statement = statements[index]
    +      if (!statement) {
    +        continue
    +      }
    +      const declaredNames = statementDeclaredNames[index] || new Set<string>()
    +
    +      const runtimeRefs = this._collectReferencedIdentifiers(statement, false)
    +      const allRefs = this._collectReferencedIdentifiers(statement, true)
    +      for (const declaredName of declaredNames) {
    +        runtimeRefs.delete(declaredName)
    +        allRefs.delete(declaredName)
    +      }
    +
    +      const runtimeTopLevelDeps = new Set<string>()
    +      const allTopLevelDeps = new Set<string>()
    +      const statementRuntimeImportLocals = new Set<string>()
    +      const statementTypeImportLocals = new Set<string>()
    +
    +      for (const ref of runtimeRefs) {
    +        if (topLevelNames.has(ref)) {
    +          runtimeTopLevelDeps.add(ref)
    +        }
    +        if (runtimeImportLocals.has(ref)) {
    +          statementRuntimeImportLocals.add(ref)
    +        }
    +      }
    +
    +      for (const ref of allRefs) {
    +        if (topLevelNames.has(ref)) {
    +          allTopLevelDeps.add(ref)
    +        }
    +        if (typeImportLocals.has(ref)) {
    +          statementTypeImportLocals.add(ref)
    +        }
    +      }
    +
    +      statementInfo.push({
    +        declaredNames,
    +        runtimeTopLevelDeps,
    +        allTopLevelDeps,
    +        runtimeImportLocals: statementRuntimeImportLocals,
    +        typeImportLocals: statementTypeImportLocals,
    +      })
    +    }
    +
    +    return {
    +      moduleKey,
    +      modulePath,
    +      params: moduleParams,
    +      sourceFile,
    +      hasExternalImports: this._hasExternalImports(sourceFile),
    +      statements,
    +      statementInfo,
    +      declarationsByName,
    +      exportToLocalName,
    +      importSpecsByLocalName,
    +    }
    +  }
    +
    +  _selectStandaloneModuleSymbols(
    +    info: StandaloneModuleInfo,
    +    runtimeRequiredNames: Set<string>,
    +    typeRequiredNames: Set<string>,
    +    mode: StandaloneMode,
    +  ): StandaloneModuleSelection {
    +    const includeTypeDeps = mode === 'ts'
    +    const pendingLocalNames: string[] = []
    +    const visitedLocalNames = new Set<string>()
    +
    +    const seedLocalName = (requiredExportName: string): void => {
    +      const localName = info.exportToLocalName.get(requiredExportName) || requiredExportName
    +      if (!info.declarationsByName.has(localName) || visitedLocalNames.has(localName)) {
    +        return
    +      }
    +      pendingLocalNames.push(localName)
    +    }
    +
    +    for (const runtimeRequiredName of runtimeRequiredNames) {
    +      seedLocalName(runtimeRequiredName)
    +    }
    +    for (const typeRequiredName of typeRequiredNames) {
    +      seedLocalName(typeRequiredName)
    +    }
    +
    +    const includedStatementIndexes = new Set<number>()
    +    while (pendingLocalNames.length > 0) {
    +      const localName = pendingLocalNames.pop()
    +      if (!localName || visitedLocalNames.has(localName)) {
    +        continue
    +      }
    +      visitedLocalNames.add(localName)
    +
    +      const statementIndexes = info.declarationsByName.get(localName)
    +      if (!statementIndexes) {
    +        continue
    +      }
    +
    +      for (const statementIndex of statementIndexes) {
    +        if (includedStatementIndexes.has(statementIndex)) {
    +          continue
    +        }
    +        includedStatementIndexes.add(statementIndex)
    +
    +        const statementMeta = info.statementInfo[statementIndex]
    +        if (!statementMeta) {
    +          continue
    +        }
    +        const deps = includeTypeDeps ? statementMeta.allTopLevelDeps : statementMeta.runtimeTopLevelDeps
    +        for (const depName of deps) {
    +          if (!visitedLocalNames.has(depName)) {
    +            pendingLocalNames.push(depName)
    +          }
    +        }
    +      }
    +    }
    +
    +    const depRuntimeNames = new Map<string, Set<string>>()
    +    const depTypeNames = new Map<string, Set<string>>()
    +    const runtimeAliases = new Set<string>()
    +    const wrapperAliases = new Set<string>()
    +    const wrapperRenameByStatementIndex = new Map<number, string>()
    +    const typeAliases = new Set<string>()
    +
    +    for (const statementIndex of includedStatementIndexes) {
    +      const statementMeta = info.statementInfo[statementIndex]
    +      if (!statementMeta) {
    +        continue
    +      }
    +
    +      for (const runtimeImportLocal of statementMeta.runtimeImportLocals) {
    +        const importSpecs = info.importSpecsByLocalName.get(runtimeImportLocal) || []
    +        for (const importSpec of importSpecs) {
    +          if (importSpec.isTypeOnly) {
    +            continue
    +          }
    +          this._addRequiredName(depRuntimeNames, importSpec.depKey, importSpec.importedName)
    +          if (importSpec.localName !== importSpec.importedName) {
    +            runtimeAliases.add(`const ${importSpec.localName} = ${importSpec.importedName};`)
    +          }
    +        }
    +      }
    +
    +      if (!includeTypeDeps) {
    +        continue
    +      }
    +
    +      for (const typeImportLocal of statementMeta.typeImportLocals) {
    +        const importSpecs = info.importSpecsByLocalName.get(typeImportLocal) || []
    +        for (const importSpec of importSpecs) {
    +          this._addRequiredName(depTypeNames, importSpec.depKey, importSpec.importedName)
    +          if (importSpec.localName !== importSpec.importedName) {
    +            typeAliases.add(`type ${importSpec.localName} = ${importSpec.importedName}`)
    +          }
    +        }
    +      }
    +    }
    +
    +    this._collapseStandaloneForwardingWrappers(
    +      info,
    +      includedStatementIndexes,
    +      wrapperAliases,
    +      wrapperRenameByStatementIndex,
    +      runtimeRequiredNames,
    +      mode,
    +    )
    +
    +    return {
    +      includedStatementIndexes,
    +      depRuntimeNames,
    +      depTypeNames,
    +      runtimeAliases,
    +      wrapperAliases,
    +      wrapperRenameByStatementIndex,
    +      typeAliases,
    +    }
    +  }
    +
    +  _collapseStandaloneForwardingWrappers(
    +    info: StandaloneModuleInfo,
    +    includedStatementIndexes: Set<number>,
    +    wrapperAliases: Set<string>,
    +    wrapperRenameByStatementIndex: Map<number, string>,
    +    runtimeRequiredNames: Set<string>,
    +    mode: StandaloneMode,
    +  ): void {
    +    if (mode !== 'js') {
    +      return
    +    }
    +
    +    const sortedIndexes = [...includedStatementIndexes].sort((a, b) => b - a)
    +    for (const statementIndex of sortedIndexes) {
    +      const aliasSpec = this._getStandaloneForwardingWrapperAlias(info, statementIndex, includedStatementIndexes)
    +      if (!aliasSpec) {
    +        continue
    +      }
    +
    +      includedStatementIndexes.delete(statementIndex)
    +      if (
    +        this._canRenameStandaloneWrapperTarget(
    +          info,
    +          aliasSpec,
    +          includedStatementIndexes,
    +          statementIndex,
    +          runtimeRequiredNames,
    +        )
    +      ) {
    +        wrapperRenameByStatementIndex.set(aliasSpec.targetDeclarationIndex, aliasSpec.wrapperName)
    +        continue
    +      }
    +      wrapperAliases.add(`const ${aliasSpec.wrapperName} = ${aliasSpec.targetName};`)
    +    }
    +  }
    +
    +  _getStandaloneForwardingWrapperAlias(
    +    info: StandaloneModuleInfo,
    +    statementIndex: number,
    +    includedStatementIndexes: Set<number>,
    +  ): { wrapperName: string; targetName: string; targetDeclarationIndex: number } | null {
    +    const statement = info.statements[statementIndex]
    +    if (!statement || !ts.isFunctionDeclaration(statement) || !statement.name || !statement.body) {
    +      return null
    +    }
    +
    +    const wrapperName = statement.name.text
    +    if (wrapperName === info.params.func_name) {
    +      // Keep the root function declaration visible in standalone output.
    +      return null
    +    }
    +
    +    if (statement.body.statements.length !== 1) {
    +      return null
    +    }
    +
    +    const onlyBodyStatement = statement.body.statements[0]
    +    if (!onlyBodyStatement || !ts.isReturnStatement(onlyBodyStatement) || !onlyBodyStatement.expression) {
    +      return null
    +    }
    +
    +    const returnExpression = onlyBodyStatement.expression
    +    if (!ts.isCallExpression(returnExpression) || !ts.isIdentifier(returnExpression.expression)) {
    +      return null
    +    }
    +
    +    const targetName = returnExpression.expression.text
    +    if (targetName === wrapperName) {
    +      return null
    +    }
    +
    +    if (statement.parameters.length !== returnExpression.arguments.length) {
    +      return null
    +    }
    +
    +    for (let index = 0; index < statement.parameters.length; index++) {
    +      const parameter = statement.parameters[index]
    +      const argument = returnExpression.arguments[index]
    +      if (
    +        !parameter ||
    +        !argument ||
    +        !ts.isIdentifier(parameter.name) ||
    +        !ts.isIdentifier(argument) ||
    +        parameter.dotDotDotToken ||
    +        parameter.initializer ||
    +        parameter.questionToken ||
    +        parameter.name.text !== argument.text
    +      ) {
    +        return null
    +      }
    +    }
    +
    +    const targetDeclarationIndexes = info.declarationsByName.get(targetName)
    +    if (!targetDeclarationIndexes) {
    +      return null
    +    }
    +
    +    for (const targetDeclarationIndex of targetDeclarationIndexes) {
    +      if (!includedStatementIndexes.has(targetDeclarationIndex)) {
    +        continue
    +      }
    +      const targetStatement = info.statements[targetDeclarationIndex]
    +      if (
    +        targetStatement &&
    +        ts.isFunctionDeclaration(targetStatement) &&
    +        targetStatement.name &&
    +        targetStatement.name.text === targetName
    +      ) {
    +        return { wrapperName, targetName, targetDeclarationIndex }
    +      }
    +    }
    +
    +    return null
    +  }
    +
    +  _canRenameStandaloneWrapperTarget(
    +    info: StandaloneModuleInfo,
    +    aliasSpec: { wrapperName: string; targetName: string; targetDeclarationIndex: number },
    +    includedStatementIndexes: Set<number>,
    +    wrapperStatementIndex: number,
    +    runtimeRequiredNames: Set<string>,
    +  ): boolean {
    +    if (runtimeRequiredNames.has(aliasSpec.targetName)) {
    +      return false
    +    }
    +
    +    const otherWrapperDeclarations = info.declarationsByName.get(aliasSpec.wrapperName)
    +    if (otherWrapperDeclarations) {
    +      for (const declarationIndex of otherWrapperDeclarations) {
    +        if (declarationIndex !== wrapperStatementIndex) {
    +          return false
    +        }
    +      }
    +    }
    +
    +    for (const statementIndex of includedStatementIndexes) {
    +      const statement = info.statements[statementIndex]
    +      if (!statement) {
    +        continue
    +      }
    +
    +      if (statementIndex === aliasSpec.targetDeclarationIndex) {
    +        if (this._statementContainsIdentifierReference(statement, aliasSpec.targetName, true)) {
    +          return false
    +        }
    +        continue
    +      }
    +
    +      if (this._statementContainsIdentifierReference(statement, aliasSpec.targetName, false)) {
             return false
           }
    +    }
    +
    +    return true
    +  }
    +
    +  _statementContainsIdentifierReference(
    +    statement: ts.Statement,
    +    identifierName: string,
    +    ignoreFunctionDeclarationName: boolean,
    +  ): boolean {
    +    let found = false
    +
    +    const visit = (node: ts.Node): void => {
    +      if (found) {
    +        return
    +      }
    +
    +      if (ts.isIdentifier(node) && node.text === identifierName) {
    +        if (ignoreFunctionDeclarationName && ts.isFunctionDeclaration(node.parent) && node.parent.name === node) {
    +          // Declaration name can be renamed safely.
    +        } else {
    +          found = true
    +          return
    +        }
    +      }
    +
    +      ts.forEachChild(node, visit)
    +    }
    +
    +    ts.forEachChild(statement, visit)
    +    return found
    +  }
    +
    +  _collapseStandaloneJsPassthroughWrappers(code: string, rootFunctionName: string): string {
    +    let nextCode = code
    +    const maxPasses = 4
    +
    +    for (let pass = 0; pass < maxPasses; pass++) {
    +      const sourceFile = ts.createSourceFile('standalone.js', nextCode, ts.ScriptTarget.ES2022, true, ts.ScriptKind.JS)
    +      const topLevelDeclarationCounts = new Map<string, number>()
    +      for (const statement of sourceFile.statements) {
    +        const declaredNames = this._collectStatementDeclaredNames(statement)
    +        for (const declaredName of declaredNames) {
    +          topLevelDeclarationCounts.set(declaredName, (topLevelDeclarationCounts.get(declaredName) || 0) + 1)
    +        }
    +      }
    +
    +      const wrapperCandidates = new Map<
    +        string,
    +        { name: string; statementIndex: number; calleeText: string; declarationName: ts.Identifier }
    +      >()
    +      for (let statementIndex = 0; statementIndex < sourceFile.statements.length; statementIndex++) {
    +        const statement = sourceFile.statements[statementIndex]
    +        if (!statement) {
    +          continue
    +        }
    +        const wrapper = this._extractStandaloneJsPassthroughWrapper(statement)
    +        if (!wrapper) {
    +          continue
    +        }
    +        if (wrapper.name === rootFunctionName) {
    +          continue
    +        }
    +        if ((topLevelDeclarationCounts.get(wrapper.name) || 0) !== 1) {
    +          continue
    +        }
    +        wrapperCandidates.set(wrapper.name, {
    +          name: wrapper.name,
    +          statementIndex,
    +          calleeText: wrapper.callee.getText(sourceFile),
    +          declarationName: wrapper.declarationName,
    +        })
    +      }
    +
    +      if (wrapperCandidates.size === 0) {
    +        break
    +      }
    +
    +      const removableWrappers = new Map<
    +        string,
    +        { statementIndex: number; calleeText: string; callCalleeIdentifiers: ts.Identifier[] }
    +      >()
    +
    +      for (const [wrapperName, wrapper] of wrapperCandidates.entries()) {
    +        let hasNonCallReference = false
    +        let hasNestedDeclaration = false
    +        const callCalleeIdentifiers: ts.Identifier[] = []
    +
    +        const visit = (node: ts.Node): void => {
    +          if (hasNonCallReference || hasNestedDeclaration) {
    +            return
    +          }
    +
    +          if (ts.isIdentifier(node) && node.text === wrapperName) {
    +            if (node === wrapper.declarationName) {
    +              return
    +            }
    +
    +            if (this._isDeclarationNameIdentifier(node)) {
    +              hasNestedDeclaration = true
    +              return
    +            }
    +
    +            if (ts.isCallExpression(node.parent) && node.parent.expression === node) {
    +              callCalleeIdentifiers.push(node)
    +              return
    +            }
    +
    +            hasNonCallReference = true
    +            return
    +          }
    +
    +          ts.forEachChild(node, visit)
    +        }
    +
    +        ts.forEachChild(sourceFile, visit)
    +        if (!hasNonCallReference && !hasNestedDeclaration) {
    +          removableWrappers.set(wrapperName, {
    +            statementIndex: wrapper.statementIndex,
    +            calleeText: wrapper.calleeText,
    +            callCalleeIdentifiers,
    +          })
    +        }
    +      }
    +
    +      if (removableWrappers.size === 0) {
    +        break
    +      }
    +
    +      const edits: Array<{ start: number; end: number; text: string }> = []
    +      for (const removableWrapper of removableWrappers.values()) {
    +        const statement = sourceFile.statements[removableWrapper.statementIndex]
    +        if (statement) {
    +          edits.push({
    +            start: statement.getStart(sourceFile),
    +            end: statement.getEnd(),
    +            text: '',
    +          })
    +        }
    +
    +        for (const callIdentifier of removableWrapper.callCalleeIdentifiers) {
    +          edits.push({
    +            start: callIdentifier.getStart(sourceFile),
    +            end: callIdentifier.getEnd(),
    +            text: removableWrapper.calleeText,
    +          })
    +        }
    +      }
    +
    +      if (edits.length === 0) {
    +        break
    +      }
    +
    +      edits.sort((a, b) => b.start - a.start)
    +      let updatedCode = nextCode
    +      for (const edit of edits) {
    +        updatedCode = updatedCode.slice(0, edit.start) + edit.text + updatedCode.slice(edit.end)
    +      }
    +
    +      if (updatedCode === nextCode) {
    +        break
    +      }
    +      nextCode = updatedCode
    +    }
    +
    +    return nextCode
    +  }
    +
    +  _isDeclarationNameIdentifier(node: ts.Identifier): boolean {
    +    if (
    +      (ts.isFunctionDeclaration(node.parent) ||
    +        ts.isFunctionExpression(node.parent) ||
    +        ts.isClassDeclaration(node.parent) ||
    +        ts.isClassExpression(node.parent) ||
    +        ts.isTypeAliasDeclaration(node.parent) ||
    +        ts.isInterfaceDeclaration(node.parent) ||
    +        ts.isEnumDeclaration(node.parent) ||
    +        ts.isVariableDeclaration(node.parent) ||
    +        ts.isParameter(node.parent) ||
    +        ts.isImportClause(node.parent) ||
    +        ts.isImportSpecifier(node.parent) ||
    +        ts.isNamespaceImport(node.parent) ||
    +        ts.isImportEqualsDeclaration(node.parent) ||
    +        ts.isBindingElement(node.parent)) &&
    +      node.parent.name === node
    +    ) {
    +      return true
    +    }
    +
    +    return false
    +  }
    +
    +  _extractStandaloneJsPassthroughWrapper(
    +    statement: ts.Statement,
    +  ): { name: string; callee: ts.Expression; declarationName: ts.Identifier } | null {
    +    if (ts.isFunctionDeclaration(statement) && statement.name && statement.body) {
    +      const passthroughCall = this._extractPassthroughCallFromBody(statement.parameters, statement.body)
    +      if (!passthroughCall) {
    +        return nul
    ... [truncated]
    
  • src/_util/vendor.d.ts+31 0 added
    @@ -0,0 +1,31 @@
    +declare module 'globby' {
    +  interface Globby {
    +    (patterns: string | readonly string[], options?: unknown): Promise<string[]>
    +    sync(patterns: string | readonly string[], options?: unknown): string[]
    +  }
    +
    +  const globby: Globby
    +  export default globby
    +}
    +
    +declare module 'indent-string' {
    +  export default function indentString(input: string, indent?: string, count?: number): string
    +}
    +
    +declare module 'js-yaml' {
    +  interface YamlApi {
    +    dump(value: unknown, options?: unknown): string
    +  }
    +
    +  const YAML: YamlApi
    +  export default YAML
    +}
    +
    +declare module 'lodash' {
    +  interface LodashApi {
    +    flattenDeep<T>(array: unknown[]): T[]
    +  }
    +
    +  const _: LodashApi
    +  export default _
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.