This project ports behavior from Google's JavaScript libphonenumber implementation into Objective-C. Metadata parity and test parity are separate concerns:
- Metadata parity means the generated metadata files come from the expected Google libphonenumber ref.
- Test parity means every relevant upstream JavaScript test has a local Objective-C XCTest counterpart.
- API and logic parity means public upstream behavior is represented in the Objective-C API surface and implementation, even when method names are idiomatic Objective-C rather than exact JavaScript names.
Use this guide whenever updating metadata, porting upstream behavior, or reviewing changes that may drift from Google libphonenumber.
The parity check tracks these upstream JavaScript test files:
javascript/i18n/phonenumbers/phonenumberutil_test.jsjavascript/i18n/phonenumbers/asyoutypeformatter_test.jsjavascript/i18n/phonenumbers/shortnumberinfo_test.js
The corresponding local XCTest files are:
libPhoneNumberTests/NBPhoneNumberUtilTest.mlibPhoneNumberTests/NBAsYouTypeFormatterTest.mlibPhoneNumberShortNumberTests/NBShortNumberInfoTest.m
The upstream implementation files to inspect when porting behavior are:
javascript/i18n/phonenumbers/phonenumberutil.jsjavascript/i18n/phonenumbers/asyoutypeformatter.jsjavascript/i18n/phonenumbers/shortnumberinfo.js
Run against Google master:
swift scripts/checkUpstreamTestParity.swiftRun against a tagged release, branch, or commit:
swift scripts/checkUpstreamTestParity.swift --upstream-ref v9.0.19
swift scripts/checkUpstreamTestParity.swift --upstream-ref master
swift scripts/checkUpstreamTestParity.swift --upstream-ref <commit-sha>Run against already-downloaded upstream files:
mkdir -p /tmp/libphone-js-upstream
curl -fsSL https://raw.githubusercontent.com/google/libphonenumber/master/javascript/i18n/phonenumbers/phonenumberutil_test.js -o /tmp/libphone-js-upstream/phonenumberutil_test.js
curl -fsSL https://raw.githubusercontent.com/google/libphonenumber/master/javascript/i18n/phonenumbers/asyoutypeformatter_test.js -o /tmp/libphone-js-upstream/asyoutypeformatter_test.js
curl -fsSL https://raw.githubusercontent.com/google/libphonenumber/master/javascript/i18n/phonenumbers/shortnumberinfo_test.js -o /tmp/libphone-js-upstream/shortnumberinfo_test.js
swift scripts/checkUpstreamTestParity.swift --upstream-dir /tmp/libphone-js-upstreamThe script compares upstream function test... names with local - (void)test... method names. It normalizes known naming differences such as normalise versus normalize, geographical versus geographic, and dialling versus dialing.
Run against Google master:
swift scripts/checkUpstreamAPIParity.swiftRun against a tagged release, branch, or commit:
swift scripts/checkUpstreamAPIParity.swift --upstream-ref v9.0.19
swift scripts/checkUpstreamAPIParity.swift --upstream-ref master
swift scripts/checkUpstreamAPIParity.swift --upstream-ref <commit-sha>Run against already-downloaded upstream implementation files:
mkdir -p /tmp/libphone-js-upstream-src
curl -fsSL https://raw.githubusercontent.com/google/libphonenumber/master/javascript/i18n/phonenumbers/phonenumberutil.js -o /tmp/libphone-js-upstream-src/phonenumberutil.js
curl -fsSL https://raw.githubusercontent.com/google/libphonenumber/master/javascript/i18n/phonenumbers/asyoutypeformatter.js -o /tmp/libphone-js-upstream-src/asyoutypeformatter.js
curl -fsSL https://raw.githubusercontent.com/google/libphonenumber/master/javascript/i18n/phonenumbers/shortnumberinfo.js -o /tmp/libphone-js-upstream-src/shortnumberinfo.js
swift scripts/checkUpstreamAPIParity.swift --upstream-dir /tmp/libphone-js-upstream-srcThe API checker compares Google JS public prototype methods against the local ObjC public headers. It intentionally allows known idiomatic ObjC selector differences, such as getExpectedCostForRegion mapping to expectedCostOfPhoneNumber:forRegion:.
For each missing upstream test:
- Open the upstream JavaScript test and identify the behavior being asserted.
- Port the test into the matching local XCTest file.
- If the test fails locally, inspect the corresponding upstream implementation file and port the required logic.
- Prefer ObjC method names that fit the existing project, but keep test names close enough that the parity script can match them.
- If a legitimate naming difference appears, update the normalization rules in
scripts/checkUpstreamTestParity.swiftand explain why in the PR.
Do not silence a missing test by adding an empty test method. A passing parity check should mean the behavior has a real local assertion.
Test parity is necessary, but it does not prove that the full public API surface is synchronized. When updating from upstream, also compare the upstream prototypes against Objective-C headers.
Check public upstream methods manually when reviewing a large sync:
rg -n "i18n\\.phonenumbers\\.(PhoneNumberUtil|AsYouTypeFormatter|ShortNumberInfo)\\.prototype\\.[A-Za-z0-9_]+\\s*=\\s*function" /tmp/libphone-js-upstream-srcCheck local public headers:
rg -n "^[-+] \\(" libPhoneNumber/NBPhoneNumberUtil.h libPhoneNumber/NBAsYouTypeFormatter.h libPhoneNumberShortNumber/NBShortNumberUtil.hPay special attention to:
PhoneNumberUtil.ValidationResultnumeric values.- Public metadata accessors and supported-type/calling-code accessors.
- Parsing edge cases, especially RFC3966
phone-context, extension parsing, and leading-zero handling. - Number matching behavior when raw input, country-code source, carrier code, or proto/default-only fields differ.
- Short-number APIs for supported regions, examples, cost, carrier-specific numbers, SMS services, and emergency numbers.
- Locale-sensitive geocoder tests. Convenience APIs may use system locale, so tests must be deterministic.
If upstream exposes a method that should be public in Objective-C, add it to the public header, implement it, and add a focused XCTest. If the method already exists under a legitimate ObjC selector shape, add the mapping to scripts/checkUpstreamAPIParity.swift instead of weakening the check.
Metadata-only updates should ship as patch releases. Use a minor release only when the update also adds public API, new modules, or additive behavior beyond metadata freshness.
Before updating checked-in metadata, generate a freshness report:
swift scripts/checkMetadataFreshness.swift --output .build/metadata-freshnessThis writes:
.build/metadata-freshness/metadata-diff-summary.md.build/metadata-freshness/metadata-update-issue.md.build/metadata-freshness/metadata-update-pr.md.build/metadata-freshness/metadata-update-log-entry.md
Use these files as maintenance inputs. They are review artifacts, not generated source.
For user-reported numbering-plan gaps that are not yet in upstream metadata, follow Issue-driven metadata patch policy.
For checked-in metadata updates, use the release metadata wrapper:
swift scripts/updateMetadata.swift <version-or-ref> --dry-run
swift scripts/updateMetadata.swift <version-or-ref>Examples:
swift scripts/updateMetadata.swift v9.0.31 --dry-run
swift scripts/updateMetadata.swift v9.0.31 --only main,geocodingAfter updating metadata:
- Review generated metadata diffs for the expected files only.
- Run the upstream test and API parity checks against the same upstream ref used for metadata.
- Run the full test matrix in
docs/TESTING.md. - If upstream tests changed, port the new or renamed tests before merging.
- Record the upstream comparison and validation results in
docs/METADATA_UPDATE_LOG.md.
Use the individual metadata generator scripts only when debugging or intentionally updating one metadata family outside the normal release flow.
Every PR that updates metadata or upstream-synced behavior should include:
- The Google libphonenumber ref used.
- The
docs/METADATA_UPDATE_LOG.mdentry added or updated for the change. - The test parity and API parity commands and results.
- The test commands and result.
- Any intentional ObjC/API naming differences.
- Any upstream behavior not ported, with a clear reason.
GitHub Actions runs the parity and test matrix on pull requests and pushes to master or main:
.github/workflows/ci.ymlvalidates SPM, locale-sensitive tests, release build, whitespace, and the three Xcode schemes..github/workflows/upstream-drift.ymlruns daily against Googlemasterto surface newly added upstream tests, API methods, metadata tags, and tracked source/resource changes.
The scheduled upstream drift workflow is detection-only. It does not modify checked-in source. It uploads these review artifacts when applicable:
metadata-freshness, generated byswift scripts/checkMetadataFreshness.swift --output .build/metadata-freshness --fail-on-updateupstream-source-drift, generated byswift scripts/checkUpstreamSourceDrift.swift --output .build/upstream-source-drift
The metadata freshness and Google master parity jobs remain hard-failure signals. The source/resource drift job is a soft signal because Google master can contain unreleased metadata and resource edits before a tagged libphonenumber release exists. Inspect the uploaded upstream-source-drift artifact during metadata updates, then update metadata and port any required tests/API/logic behavior in a normal PR.