Skip to content

Fix AsYouTypeFormatter dropping parens from rule-based formats (BR, RU, CO)#3993

Open
PetroczyP wants to merge 1 commit intogoogle:masterfrom
PetroczyP:fix/437456062-aytf-parens-from-rule
Open

Fix AsYouTypeFormatter dropping parens from rule-based formats (BR, RU, CO)#3993
PetroczyP wants to merge 1 commit intogoogle:masterfrom
PetroczyP:fix/437456062-aytf-parens-from-rule

Conversation

@PetroczyP
Copy link
Copy Markdown

Summary

Fixes Issue Tracker #437456062
where AsYouTypeFormatter drops the parenthesis literals from countries whose
national format derives them from nationalPrefixFormattingRule rather than
from the inline <format> template — most visibly for BR mobile, RU mobile,
and CO. Static format() already produces the parens correctly using the
same metadata; the bug is isolated to AYTF's template-build path, which only
ever read getFormat() and ignored getNationalPrefixFormattingRule()'s
wrap.

Fix

A small private helper — applyNationalPrefixFormattingRule (Java) /
ApplyNationalPrefixFormattingRule (C++, PascalCase per Google C++ style) /
applyNationalPrefixFormattingRule_ (JS, with the Closure private-name
underscore) — is invoked at two sites in each language,
createFormattingTemplate and attemptToFormatAccruedDigits, to splice the
rule's punctuation into the format string before the existing template
substitution runs. Three branches:

  1. Empty rule or international-mode → return the bare format unchanged
    (preserves current behavior for US, AR, DE, etc. and for the
    format(num, INTERNATIONAL) rendering path).
  2. Rule begins with the national prefix (e.g. "$NP $FG" after metadata
    substitution → "0 $1" for GB, "8 $1" for RU): strip the leading prefix
    plus any adjacent separator, then splice the remainder. The trunk prefix
    continues to render separately via prefixBeforeNationalNumber, so this
    avoids doubling it.
  3. Rule contains the prefix in a position not stripped (e.g. GB
    "($NP$FG)" which wraps the prefix inside literal punctuation): return
    the bare format. Splicing such a rule would duplicate the trunk prefix
    that AYTF renders separately.

A complementary defensive fallback at the country-code-extraction function
(attemptToExtractCountryCallingCode in Java, AttemptToExtractCountryCode
in C++, attemptToExtractCountryCallingCode_ in JS) preserves the
"currentMetadata is never null" invariant the helper relies on: when the
country code maps to a non-geographical region (e.g. +800 toll-free) and
getMetadataForNonGeographicalRegion returns null because the metadata
isn't loaded, fall back to the existing EMPTY_METADATA sentinel. Same
pattern as the constructor and the regular-region path.

Test plan

  • BR fixed-line: (11) 3234-5678 rendered char-by-char against region BR.
    Single test method per language asserts every digit boundary, covering
    both the final-step assertion and the intermediate steps where parens
    first materialise. (BR test metadata only carries a fixed-line
    numberFormat; the ($FG) rule shape is identical to production BR
    mobile, so a fix verified on BR fixed-line covers the reported BR
    mobile case.)
  • RU mobile: 8 (912) 345-67-89 rendered char-by-char against region RU,
    including the closing ) that materialises only after a later
    subscriber digit advances lastMatchPosition past it (transition
    8 (9128 (912) 3 at digit 5).
  • CO breadth: (601) 1234567 rendered char-by-char against region CO,
    same ($FG) rule shape on a different pattern length to prove the fix
    is structural rather than BR-specific.
  • International-format regression: +55 11 3234-5678 rendered against
    region ZZ to confirm the isInternationalMode short-circuit returns
    the bare format (matching format(num, INTERNATIONAL)).
  • Pre-existing US (no rule), AR (rule $NP$FG), GB (rule ($NP$FG))
    AYTF tests all continue to pass — the no-change paths are byte-identical.
  • Full Java suite: mvn -pl libphonenumber -am test -B → 287 tests pass,
    0 failures.
  • Full C++ suite: ./libphonenumber_test → 331 tests pass across 16
    suites, 0 failures.
  • Full JS suite: python -m scripts.run_js_tests → all 3 test pages
    (phonenumberutil, asyoutypeformatter, shortnumberinfo) green via
    Closure jsunit + Playwright.

CONTRIBUTING.md compliance

  • CLA signed (Google Individual CLA on petroczyp@gmail.com,
    2026-05-02).
  • Bug fix, not a metadata edit. resources/PhoneNumberMetadata.xml
    and all auto-generated metadata derivatives are unchanged. The only
    metadata-XML edit is to PhoneNumberMetadataForTesting.xml (test
    fixtures) plus its regenerated derivatives (test_metadata.cc,
    PhoneNumberMetadataProtoForTesting_{BR,CO,RU}, metadatafortesting.js).
  • Three-language port: Java + C++ + JS in the same PR per the
    cross-language porting expectation. Every change in
    AsYouTypeFormatter.java has a parallel in asyoutypeformatter.cc
    and asyoutypeformatter.js.
  • Minimal-diff: ~30 LOC source + ~80 LOC test per language; no public
    API surface changed; applyNationalPrefixFormattingRule[_] is
    private in all three.
  • No documentation outside the source files; no README updates; no
    docstring sweeps.

Files

Language Source Test Test fixtures
Java java/libphonenumber/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java java/libphonenumber/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java data/PhoneNumberMetadataProtoForTesting_BR, _CO, _RU
C++ cpp/src/phonenumbers/asyoutypeformatter.h, .cc cpp/test/phonenumbers/asyoutypeformatter_test.cc cpp/src/phonenumbers/test_metadata.cc
JS javascript/i18n/phonenumbers/asyoutypeformatter.js javascript/i18n/phonenumbers/asyoutypeformatter_test.js javascript/i18n/phonenumbers/metadatafortesting.js

Test fixtures and test_metadata.cc are regenerated outputs of
BuildMetadataProtoFromXml / BuildMetadataCppFromXml /
BuildMetadataJsonFromXml against resources/PhoneNumberMetadataForTesting.xml.

AsYouTypeFormatter rendered the bare format() string and ignored
nationalPrefixFormattingRule, so any punctuation supplied by the rule
(e.g. parens around the area code) was lost as the user typed.
Affected regions include BR mobile, RU mobile, and CO; static
format() already handled them correctly via the same metadata, since
the rule is only applied on the AYTF template-build path.

Adds a private applyNationalPrefixFormattingRule helper in
AsYouTypeFormatter (Java/C++/JS) that splices the rule's punctuation
into the format template at both call sites (createFormattingTemplate
and attemptToFormatAccruedDigits). Three branches: empty rule or
international-mode renders the bare format; rule beginning with the
national prefix has the prefix and adjacent separator stripped, then
the remainder spliced; rule that wraps the prefix in literal
punctuation falls back to the bare format to avoid doubling the trunk
prefix that AYTF renders separately via prefixBeforeNationalNumber.

Also defensively falls back to EMPTY_METADATA when
GetMetadataForNonGeographicalRegion returns null (unrecognised
calling code), since the new helper now reads the current metadata's
national prefix.

Tests cover BR, RU, CO, the international-format path, and the
existing US/AR/DE/AO/IT/GB regression guards in all three languages.
Test-only metadata regenerated for BR/RU/CO; production
PhoneNumberMetadata.xml is untouched.

Fixes Issue Tracker #437456062.
@PetroczyP PetroczyP requested a review from a team as a code owner May 5, 2026 04:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant