Repository: psf/black

Issue:
Suggestion: use lowercase letters in hex literals
Reading the code, when I saw the commend saying `All letters used in the representation are normalized to lowercase`, I thought it also refers to hex letters. However, a few lines later, I was disappointed to see `Change hex literals to upper case.`.

https://github.com/psf/black/blob/6284953d07060804dcdeacf4626b76aed7a20683/src/black/__init__.py#L5158-L5168

There's one practical issue with use of upper-case hex letters, though, and that's similarity between letter `B` and digit `8`.

I have seen bugs caused by this similarity in security-sensitive code, such as UTF-8/-16 decoders. Personally, I always used uppercase letters in hex numerals prior to that incident, because of the aesthetics of it; but, since then, have been switching to lowercase anywhere possible.

Maybe Black would consider switching to lowercase hex letters, as well?


Discussion:
I initially implemented it that way but it was changed in #530 for a reason I no longer recall. @zsol do you remember why we made that change? 
See also recent https://github.com/psf/black/issues/1514, asking for lowercase hexadecimal due to the similarity of `A` and `4`.
> I initially implemented it that way but it was changed in #530 for a reason I no longer recall. @zsol do you remember why we made that change?

https://github.com/psf/black/issues/467 was a month before #530, where lowercase hex was initially decided upon. No reasoning given, but perhaps it'll help jog the memory. 
I really can't recall anymore, and couldn't find a mention of this in our discussions either, but I'm sure there _was_ discussion.
ABB4AB8A
vs
abb4ab8a

isn't this enough to settle the issue?
:-)
I'll gladly fix this if everyone agrees on it? :) 
I've heard no contrarian opinions so far
I agree that lowercase is better in a vacuum. I'm not as sure if this change is worth the pain of changing Black's existing style. Every time we change Black's current style, that means people who use Black to format an existing codebase need to reformat their code, which pollutes their commit histories and might break open branches. So to decide whether to make a formatting change, we need to see if the gains from the formatting change outweigh the pains of changing existing formatting. We don't have a good framework for making that tradeoff, but it's worth thinking about.
Sure, let's think about this.
Anyway, IMO one single commit to catch up with this change is not dramatic for projects adopting black

Resolving patch:
diff --git a/src/black/__init__.py b/src/black/__init__.py
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -5192,31 +5192,52 @@ def normalize_numeric_literal(leaf: Leaf) -> None:
         # Leave octal and binary literals alone.
         pass
     elif text.startswith("0x"):
-        # Change hex literals to upper case.
-        before, after = text[:2], text[2:]
-        text = f"{before}{after.upper()}"
+        text = format_hex(text)
     elif "e" in text:
-        before, after = text.split("e")
-        sign = ""
-        if after.startswith("-"):
-            after = after[1:]
-            sign = "-"
-        elif after.startswith("+"):
-            after = after[1:]
-        before = format_float_or_int_string(before)
-        text = f"{before}e{sign}{after}"
+        text = format_scientific_notation(text)
     elif text.endswith(("j", "l")):
-        number = text[:-1]
-        suffix = text[-1]
-        # Capitalize in "2L" because "l" looks too similar to "1".
-        if suffix == "l":
-            suffix = "L"
-        text = f"{format_float_or_int_string(number)}{suffix}"
+        text = format_long_or_complex_number(text)
     else:
         text = format_float_or_int_string(text)
     leaf.value = text
 
 
+def format_hex(text: str) -> str:
+    """
+    Formats a hexadecimal string like "0x12b3"
+
+    Uses lowercase because of similarity between "B" and "8", which
+    can cause security issues.
+    see: https://github.com/psf/black/issues/1692
+    """
+
+    before, after = text[:2], text[2:]
+    return f"{before}{after.lower()}"
+
+
+def format_scientific_notation(text: str) -> str:
+    """Formats a numeric string utilizing scentific notation"""
+    before, after = text.split("e")
+    sign = ""
+    if after.startswith("-"):
+        after = after[1:]
+        sign = "-"
+    elif after.startswith("+"):
+        after = after[1:]
+    before = format_float_or_int_string(before)
+    return f"{before}e{sign}{after}"
+
+
+def format_long_or_complex_number(text: str) -> str:
+    """Formats a long or complex string like `10L` or `10j`"""
+    number = text[:-1]
+    suffix = text[-1]
+    # Capitalize in "2L" because "l" looks too similar to "1".
+    if suffix == "l":
+        suffix = "L"
+    return f"{format_float_or_int_string(number)}{suffix}"
+
+
 def format_float_or_int_string(text: str) -> str:
     """Formats a float string like "1.0"."""
     if "." not in text:
diff --git a/src/blib2to3/pytree.py b/src/blib2to3/pytree.py
--- a/src/blib2to3/pytree.py
+++ b/src/blib2to3/pytree.py
@@ -34,7 +34,7 @@
 import sys
 from io import StringIO
 
-HUGE: int = 0x7FFFFFFF  # maximum repeat count, default max
+HUGE: int = 0x7fffffff  # maximum repeat count, default max
 
 _type_reprs: Dict[int, Union[Text, int]] = {}
 


Test patch:
diff --git a/test_requirements.txt b/test_requirements.txt
--- a/test_requirements.txt
+++ b/test_requirements.txt
@@ -2,4 +2,4 @@ pytest >= 6.1.1
 pytest-mock >= 3.3.1
 pytest-cases >= 2.3.0
 coverage >= 5.3
-parameterized >= 0.7.4
\ No newline at end of file
+parameterized >= 0.7.4
diff --git a/tests/data/numeric_literals.py b/tests/data/numeric_literals.py
--- a/tests/data/numeric_literals.py
+++ b/tests/data/numeric_literals.py
@@ -12,7 +12,7 @@
 x = 123456789E123456789
 x = 123456789J
 x = 123456789.123456789J
-x = 0XB1ACC
+x = 0Xb1aCc
 x = 0B1011
 x = 0O777
 x = 0.000000006
@@ -36,7 +36,7 @@
 x = 123456789e123456789
 x = 123456789j
 x = 123456789.123456789j
-x = 0xB1ACC
+x = 0xb1acc
 x = 0b1011
 x = 0o777
 x = 0.000000006
diff --git a/tests/data/numeric_literals_py2.py b/tests/data/numeric_literals_py2.py
--- a/tests/data/numeric_literals_py2.py
+++ b/tests/data/numeric_literals_py2.py
@@ -3,7 +3,7 @@
 x = 123456789L
 x = 123456789l
 x = 123456789
-x = 0xb1acc
+x = 0xB1aCc
 
 # output
 
@@ -13,4 +13,4 @@
 x = 123456789L
 x = 123456789L
 x = 123456789
-x = 0xB1ACC
+x = 0xb1acc
diff --git a/tests/data/numeric_literals_skip_underscores.py b/tests/data/numeric_literals_skip_underscores.py
--- a/tests/data/numeric_literals_skip_underscores.py
+++ b/tests/data/numeric_literals_skip_underscores.py
@@ -3,7 +3,7 @@
 x = 123456789
 x = 1_2_3_4_5_6_7
 x = 1E+1
-x = 0xb1acc
+x = 0xb1AcC
 x = 0.00_00_006
 x = 12_34_567J
 x = .1_2
@@ -16,8 +16,8 @@
 x = 123456789
 x = 1_2_3_4_5_6_7
 x = 1e1
-x = 0xB1ACC
+x = 0xb1acc
 x = 0.00_00_006
 x = 12_34_567j
 x = 0.1_2
-x = 1_2.0
\ No newline at end of file
+x = 1_2.0