{"action":"created","comment":{"url":"https://api.github.com/repos/scala-js/scala-js/pulls/comments/195931497","pull_request_review_id":129388808,"id":195931497,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5NTkzMTQ5Nw==","diff_hunk":"@@ -4,285 +4,702 @@ import scala.annotation.switch\n import scala.scalajs.js\n \n import java.io._\n-import java.lang._\n \n-final class Formatter(private val dest: Appendable) extends Closeable with Flushable {\n+final class Formatter(private[this] var dest: Appendable)\n+    extends Closeable with Flushable {\n+\n   import Formatter._\n+  import Flags._\n+\n+  /** If `dest == null`, the real content is in `stringOutput`.\n+   *\n+   *  A real `StringBuilder` may be created lazily if `out()` is called, which\n+   *  will then capture the current content of `stringOutput`.\n+   *\n+   *  This allows to bypass the allocation of the `StringBuilder`, the call\n+   *  through `dest.append()` and more importantly the `try..catch`es in the\n+   *  common case where the `Formatter` is created without a specific\n+   *  destination.\n+   */\n+  private[this] var stringOutput: String = \"\"\n+\n+  private[this] var closed: Boolean = false\n+  private[this] var lastIOException: IOException = null\n+\n+  def this() = this(null: Appendable)\n+\n+  @inline\n+  private def trapIOExceptions(body: => Unit): Unit = {\n+    try {\n+      body\n+    } catch {\n+      case th: IOException =>\n+        lastIOException = th\n+    }\n+  }\n+\n+  private def sendToDest(s: String): Unit = {\n+    if (dest eq null)\n+      stringOutput += s\n+    else\n+      sendToDestSlowPath(js.Array(s))\n+  }\n+\n+  private def sendToDest(s1: String, s2: String): Unit = {\n+    if (dest eq null)\n+      stringOutput += s1 + s2\n+    else\n+      sendToDestSlowPath(js.Array(s1, s2))\n+  }\n \n-  var closed = false\n+  private def sendToDest(s1: String, s2: String, s3: String): Unit = {\n+    if (dest eq null)\n+      stringOutput += s1 + s2 + s3\n+    else\n+      sendToDestSlowPath(js.Array(s1, s2, s3))\n+  }\n \n-  def this() = this(new StringBuilder())\n+  @noinline\n+  private def sendToDestSlowPath(ss: js.Array[String]): Unit = {\n+    trapIOExceptions {\n+      ss.foreach(dest.append(_))\n+    }\n+  }\n \n   def close(): Unit = {\n-    if (!closed) {\n+    if (!closed && (dest ne null)) {\n       dest match {\n-        case cl: Closeable => cl.close()\n+        case cl: Closeable =>\n+          trapIOExceptions {\n+            cl.close()\n+          }\n         case _ =>\n       }\n     }\n     closed = true\n   }\n \n-  def flush(): Unit = ifNotClosed {\n-    dest match {\n-      case fl: Flushable => fl.flush()\n-      case _ =>\n+  def flush(): Unit = {\n+    checkNotClosed()\n+    if (dest ne null) {\n+      dest match {\n+        case fl: Flushable =>\n+          trapIOExceptions {\n+            fl.flush()\n+          }\n+        case _ =>\n+      }\n     }\n   }\n \n-  // Begin implem of format()\n+  def format(format: String, args: Array[AnyRef]): Formatter = {\n+    // scalastyle:off return\n \n-  def format(format_in: String, args: Array[AnyRef]): Formatter = ifNotClosed {\n-    import js.JSNumberOps._\n+    checkNotClosed()\n \n-    var fmt: String = format_in\n-    var lastImplicitIndex: Int = 0\n-    var lastIndex: Int = 0 // required for < flag\n+    var lastImplicitArgIndex: Int = 0\n+    var lastArgIndex: Int = 0 // required for < flag\n \n-    while (!fmt.isEmpty) {\n-      fmt match {\n-        case RegularChunk(matchResult) =>\n-          fmt = fmt.substring(matchResult(0).get.length)\n-          dest.append(matchResult(0).get)\n+    val fmtLength = format.length\n+    var fmtIndex: Int = 0\n \n-        case DoublePercent(_) =>\n-          fmt = fmt.substring(2)\n-          dest.append('%')\n+    while (fmtIndex != fmtLength) {\n+      // Process a portion without '%'\n+      val nextPercentIndex = format.indexOf(\"%\", fmtIndex)\n+      if (nextPercentIndex < 0) {\n+        // No more '%'\n+        sendToDest(format.substring(fmtIndex))\n+        return this\n+      }\n+      sendToDest(format.substring(fmtIndex, nextPercentIndex))\n \n-        case EOLChunk(_) =>\n-          fmt = fmt.substring(2)\n-          dest.append('\\n')\n+      // Process one '%'\n \n-        case FormattedChunk(matchResult) =>\n-          fmt = fmt.substring(matchResult(0).get.length)\n+      val formatSpecifierIndex = nextPercentIndex + 1\n+      val re = FormatSpecifier\n+      re.lastIndex = formatSpecifierIndex\n+      val execResult = re.exec(format)\n \n-          val flags = matchResult(2).get\n-          def hasFlag(flag: String) = flags.indexOf(flag) >= 0\n+      if (execResult == null || execResult.index != formatSpecifierIndex) {\n+        // Could not parse a valid format specifier\n+        val conversion =\n+          if (formatSpecifierIndex == fmtLength) \"%\"\n+          else format.substring(formatSpecifierIndex, formatSpecifierIndex + 1)\n+        throw new UnknownFormatConversionException(conversion)\n+      }\n \n-          val indexStr = matchResult(1).getOrElse(\"\")\n-          val index = if (!indexStr.isEmpty) {\n-            Integer.parseInt(indexStr)\n-          } else if (hasFlag(\"<\")) {\n-            lastIndex\n+      fmtIndex = re.lastIndex // position at the end of the match\n+\n+      val conversion = format.charAt(fmtIndex - 1)\n+      val flags = parseFlags(execResult(2).asInstanceOf[String], conversion)\n+      val width = parsePositiveIntSilent(execResult(3), -1)\n+      val precision = parsePositiveIntSilent(execResult(4), -1)\n+\n+      val arg = if (conversion == '%' || conversion == 'n') {\n+        /* No argument. Make sure not to bump `lastImplicitArgIndex` nor to\n+         * affect `lastArgIndex`.\n+         */\n+        null\n+      } else {\n+        if (flags.leftAlign && width < 0)\n+          throw new MissingFormatWidthException(\"%\" + execResult(0))\n+\n+        val argIndex = if (flags.useLastIndex) {\n+          // Explicitly use the last index\n+          lastArgIndex\n+        } else {\n+          val i = parsePositiveIntSilent(execResult(1), 0)\n+          if (i == 0) {\n+            // Either there is no explicit index, or the explicit index is 0\n+            lastImplicitArgIndex += 1\n+            lastImplicitArgIndex\n+          } else if (i < 0) {\n+            // Cannot be parsed, same as useLastIndex\n+            lastArgIndex\n           } else {\n-            lastImplicitIndex += 1\n-            lastImplicitIndex\n-          }\n-          lastIndex = index\n-          if (index <= 0 || index > args.length)\n-            throw new MissingFormatArgumentException(matchResult(5).get)\n-          val arg = args(index-1)\n-\n-          val widthStr = matchResult(3).getOrElse(\"\")\n-          val hasWidth = !widthStr.isEmpty\n-          val width = {\n-            if (hasWidth) Integer.parseInt(widthStr)\n-            else if (hasFlag(\"-\")) throw new MissingFormatWidthException(format_in)\n-            else 0\n+            // Could be parsed, this is the index\n+            i\n           }\n+        }\n \n-          val precisionStr = matchResult(4).getOrElse(\"\")\n-          val hasPrecision = !precisionStr.isEmpty\n-          val precision =\n-            if (hasPrecision) Integer.parseInt(precisionStr)\n-            else 0\n+        if (argIndex <= 0 || argIndex > args.length)\n+          throw new MissingFormatArgumentException(\"%\" + execResult(0))\n \n-          val conversion = matchResult(5).get.charAt(0)\n+        lastArgIndex = argIndex\n+        args(argIndex - 1)\n+      }\n \n-          // Avoid using conversion.isUpper not to depend on the Unicode database\n-          @inline def isConversionUpperCase: scala.Boolean =\n-            conversion <= 'Z'\n+      formatArg(arg, conversion, flags, width, precision)\n+    }\n \n-          def intArg: Int = (arg: Any) match {\n-            case arg: Int  => arg\n-            case arg: Char => arg.toInt\n-          }\n-          def numberArg: scala.Double = (arg: Any) match {\n-            case arg: Number => arg.doubleValue()\n-            case arg: Char   => arg.toDouble\n-          }\n+    this\n \n-          def padCaptureSign(argStr: String, prefix: String) = {\n-            val firstChar = argStr.charAt(0)\n-            if (firstChar == '+' || firstChar == '-')\n-              pad(argStr.substring(1), firstChar+prefix)\n-            else\n-              pad(argStr, prefix)\n-          }\n+    // scalastyle:on return\n+  }\n \n-          def strRepeat(s: String, times: Int) = {\n-            var result: String = \"\"\n-            var i = times\n-            while (i > 0) {\n-              result += s\n-              i -= 1\n-            }\n-            result\n-          }\n+  private def parseFlags(flags: String, conversion: Char): Flags = {\n+    var bits = if (conversion <= 'Z') UpperCase else 0\n+\n+    val len = flags.length\n+    var i = 0\n+    while (i != len) {\n+      val f = flags.charAt(i)\n+      val bit = (f: @switch) match {\n+        case '-' => LeftAlign\n+        case '#' => AltFormat\n+        case '+' => PositivePlus\n+        case ' ' => PositiveSpace\n+        case '0' => ZeroPad\n+        case ',' => UseGroupingSeps\n+        case '(' => NegativeParen\n+        case '<' => UseLastIndex\n+      }\n \n-          def with_+(s: String, preventZero: scala.Boolean = false) = {\n-            if (s.charAt(0) != '-') {\n-              if (hasFlag(\"+\"))\n-                pad(s, \"+\", preventZero)\n-              else if (hasFlag(\" \"))\n-                pad(s, \" \", preventZero)\n-              else\n-                pad(s, \"\", preventZero)\n-            } else {\n-              if (hasFlag(\"(\"))\n-                pad(s.substring(1) + \")\", \"(\", preventZero)\n-              else\n-                pad(s.substring(1), \"-\", preventZero)\n-            }\n-          }\n+      if ((bits & bit) != 0)\n+        throw new DuplicateFormatFlagsException(f.toString)\n \n-          def pad(argStr: String, prefix: String = \"\",\n-                  preventZero: Boolean = false) = {\n-            val prePadLen = argStr.length + prefix.length\n-\n-            val padStr = {\n-              if (width <= prePadLen) {\n-                prefix + argStr\n-              } else {\n-                val padRight = hasFlag(\"-\")\n-                val padZero = hasFlag(\"0\") && !preventZero\n-                val padLength = width - prePadLen\n-                val padChar: String = if (padZero) \"0\" else \" \"\n-                val padding = strRepeat(padChar, padLength)\n-\n-                if (padZero && padRight)\n-                  throw new java.util.IllegalFormatFlagsException(flags)\n-                else if (padRight) prefix + argStr  + padding\n-                else if (padZero)  prefix + padding + argStr\n-                else padding + prefix + argStr\n-              }\n-            }\n+      bits |= bit\n+      i += 1\n+    }\n+\n+    new Flags(bits)\n+  }\n+\n+  private def parsePositiveIntSilent(capture: js.UndefOr[String],\n+      default: Int): Int = {\n+    capture.fold {\n+      default\n+    } { s =>\n+      val x = js.Dynamic.global.parseInt(s, 10).asInstanceOf[Double]\n+      if (x <= Int.MaxValue)\n+        x.toInt\n+      else\n+        -1 // Silently ignore and return -1\n+    }\n+  }\n+\n+  private def formatArg(arg: Any, conversion: Char, flags: Flags, width: Int,\n+      precision: Int): Unit = {\n+\n+    @inline def rejectPrecision(): Unit = {\n+      if (precision >= 0)\n+        throw new IllegalFormatPrecisionException(precision)\n+    }\n \n-            val casedStr =\n-              if (isConversionUpperCase) padStr.toUpperCase()\n-              else padStr\n-            dest.append(casedStr)\n+    def formatNullOrThrowIllegalFormatConversion(): Unit = {\n+      if (arg == null)\n+        formatNonNumericString(flags, width, precision, \"null\")\n+      else\n+        throw new IllegalFormatConversionException(conversion, arg.getClass)\n+    }\n+\n+    @inline def precisionWithDefault =\n+      if (precision >= 0) precision\n+      else 6\n+\n+    @inline def efgCommon(notation: (Double, Int, Boolean) => String): Unit = {\n+      arg match {\n+        case arg: Double =>\n+          if (arg.isNaN || arg.isInfinite) {\n+            formatNaNOrInfinite(flags, width, arg)\n+          } else {\n+            formatNumericString(flags, width,\n+                notation(arg, precisionWithDefault, flags.altFormat))\n           }\n+        case _ =>\n+          formatNullOrThrowIllegalFormatConversion()\n+      }\n+    }\n \n-          (conversion: @switch) match {\n-            case 'b' | 'B' => pad { arg match {\n-              case null => \"false\"\n-              case b: Boolean => String.valueOf(b)\n-              case _ => \"true\"\n-            } }\n-            case 'h' | 'H' => pad {\n-              if (arg eq null) \"null\"\n-              else Integer.toHexString(arg.hashCode)\n+    (conversion: @switch) match {\n+      case 'b' | 'B' =>\n+        validateFlags(flags, conversion, NumericOnlyFlags | AltFormat)\n+        val str =\n+          if ((arg.asInstanceOf[AnyRef] eq false.asInstanceOf[AnyRef]) || arg == null) \"false\"\n+          else \"true\"\n+        formatNonNumericString(flags, width, precision, str)\n+\n+      case 'h' | 'H' =>\n+        validateFlags(flags, conversion, NumericOnlyFlags | AltFormat)\n+        val str =\n+          if (arg == null) \"null\"\n+          else Integer.toHexString(arg.hashCode)\n+        formatNonNumericString(flags, width, precision, str)\n+\n+      case 's' | 'S' =>\n+        arg match {\n+          case formattable: Formattable =>\n+            validateFlags(flags, conversion, NumericOnlyFlags)\n+            val formattableFlags = {\n+              (if (flags.leftAlign) FormattableFlags.LEFT_JUSTIFY else 0) |\n+              (if (flags.altFormat) FormattableFlags.ALTERNATE else 0) |\n+              (if (flags.upperCase) FormattableFlags.UPPERCASE else 0)\n             }\n-            case 's' | 'S' => arg match {\n-              case formattable: Formattable =>\n-                val flags = {\n-                  (if (hasFlag(\"-\")) FormattableFlags.LEFT_JUSTIFY else 0) |\n-                  (if (hasFlag(\"#\")) FormattableFlags.ALTERNATE else 0) |\n-                  (if (isConversionUpperCase) FormattableFlags.UPPERCASE else 0)\n-                }\n-                formattable.formatTo(this, flags,\n-                    if (hasWidth) width else -1,\n-                    if (hasPrecision) precision else -1)\n-                None // no further processing\n-              case _ =>\n-                if (!hasFlag(\"#\"))\n-                  pad(String.valueOf(arg))\n-                else\n-                  throw new FormatFlagsConversionMismatchException(\"#\", 's')\n+            formattable.formatTo(this, formattableFlags, width, precision)\n+\n+          case _ =>\n+            validateFlags(flags, conversion, NumericOnlyFlags | AltFormat)\n+            val str = String.valueOf(arg)\n+            formatNonNumericString(flags, width, precision, str)\n+        }\n+\n+      case 'c' | 'C' =>\n+        validateFlags(flags, conversion, NumericOnlyFlags | AltFormat)\n+        rejectPrecision()\n+        arg match {\n+          case arg: Char =>\n+            formatNonNumericString(flags, width, -1, arg.toString)\n+          case arg: Int =>\n+            if (!Character.isValidCodePoint(arg))\n+              throw new IllegalFormatCodePointException(arg)\n+            val str = if (arg < Character.MIN_SUPPLEMENTARY_CODE_POINT) {\n+              js.Dynamic.global.String.fromCharCode(arg)\n+            } else {\n+              js.Dynamic.global.String.fromCharCode(\n+                  0xd800 | ((arg >> 10) - (0x10000 >> 10)),\n+                  0xdc00 | (arg & 0x3ff))\n             }\n-            case 'c' | 'C' =>\n-              pad(intArg.toChar.toString)\n-            case 'd' =>\n-              with_+(numberArg.toString())\n-            case 'o' =>\n-              val str = (arg: Any) match {\n-                case arg: scala.Int  => Integer.toOctalString(arg)\n-                case arg: scala.Long => Long.toOctalString(arg)\n-              }\n-              padCaptureSign(str, if (hasFlag(\"#\")) \"0\" else \"\")\n-            case 'x' | 'X' =>\n-              val str = (arg: Any) match {\n-                case arg: scala.Int  => Integer.toHexString(arg)\n-                case arg: scala.Long => Long.toHexString(arg)\n-              }\n-              padCaptureSign(str, if (hasFlag(\"#\")) \"0x\" else \"\")\n-            case 'e' | 'E' =>\n-              sciNotation(if (hasPrecision) precision else 6)\n-            case 'g' | 'G' =>\n-              val m = Math.abs(numberArg)\n-              // precision handling according to JavaDoc\n-              // precision here means number of significant digits\n-              // not digits after decimal point\n-              val p =\n-                if (!hasPrecision) 6\n-                else if (precision == 0) 1\n-                else precision\n-              // between 1e-4 and 10e(p): display as fixed\n-              if (m >= 1e-4 && m < Math.pow(10, p)) {\n-                /* First approximation of the smallest power of 10 that is >= m.\n-                 * Due to rounding errors in the event of an imprecise `log10`\n-                 * function, sig0 could actually be the smallest power of 10\n-                 * that is > m.\n-                 */\n-                val sig0 = Math.ceil(Math.log10(m)).toInt\n-                /* Increment sig0 so that it is always the first power of 10\n-                 * that is > m.\n-                 */\n-                val sig = if (Math.pow(10, sig0) <= m) sig0 + 1 else sig0\n-                with_+(numberArg.toFixed(Math.max(p - sig, 0)))\n-              } else sciNotation(p - 1)\n-            case 'f' =>\n-              with_+({\n-                // JavaDoc: 6 is default precision\n-                numberArg.toFixed(if (hasPrecision) precision else 6)\n-              }, numberArg.isNaN || numberArg.isInfinite)\n-          }\n+            formatNonNumericString(flags, width, -1, str.asInstanceOf[String])\n+          case _ =>\n+            formatNullOrThrowIllegalFormatConversion()\n+        }\n+\n+      case 'd' =>\n+        validateFlags(flags, conversion, AltFormat)\n+        rejectPrecision()\n+        arg match {\n+          case arg: Int =>\n+            formatNumericString(flags, width, arg.toString())\n+          case arg: Long =>\n+            formatNumericString(flags, width, arg.toString())\n+          case _ =>\n+            formatNullOrThrowIllegalFormatConversion()\n+        }\n+\n+      case 'o' =>\n+        validateFlags(flags, conversion, InvalidFlagsForOctalAndHex)\n+        rejectPrecision()\n+        val prefix =\n+          if (flags.altFormat) \"0\"\n+          else \"\"\n+        arg match {\n+          case arg: Int =>\n+            padAndSendToDest(flags, width, prefix,\n+                java.lang.Integer.toOctalString(arg))\n+          case arg: Long =>\n+            padAndSendToDest(flags, width, prefix,\n+                java.lang.Long.toOctalString(arg))\n+          case _ =>\n+            formatNullOrThrowIllegalFormatConversion()\n+        }\n+\n+      case 'x' | 'X' =>\n+        validateFlags(flags, conversion, InvalidFlagsForOctalAndHex)\n+        rejectPrecision()\n+        val prefix = {\n+          if (!flags.altFormat) \"\"\n+          else if (flags.upperCase) \"0X\"\n+          else \"0x\"\n+        }\n+        arg match {\n+          case arg: Int =>\n+            padAndSendToDest(flags, width, prefix,\n+                applyUpperCase(flags, java.lang.Integer.toHexString(arg)))\n+          case arg: Long =>\n+            padAndSendToDest(flags, width, prefix,\n+                applyUpperCase(flags, java.lang.Long.toHexString(arg)))\n+          case _ =>\n+            formatNullOrThrowIllegalFormatConversion()\n+        }\n+\n+      case 'e' | 'E' =>\n+        validateFlags(flags, conversion, UseGroupingSeps)\n+        efgCommon(computerizedScientificNotation _)\n+\n+      case 'g' | 'G' =>\n+        validateFlags(flags, conversion, AltFormat)\n+        efgCommon(generalScientificNotation _)\n+\n+      case 'f' =>\n+        validateFlags(flags, conversion, 0)\n+        efgCommon(decimalNotation _)\n+\n+      case '%' =>\n+        validateFlagsForPercentAndNewline(flags, conversion,\n+            AllWrittenFlags & ~LeftAlign)\n+        rejectPrecision()\n+        if (flags.leftAlign && width < 0)\n+          throw new MissingFormatWidthException(\"%-%\")\n+        padAndSendToDest(flags, width, \"%\")\n+\n+      case 'n' =>\n+        validateFlagsForPercentAndNewline(flags, conversion, AllWrittenFlags)\n+        rejectPrecision()\n+        if (width >= 0)\n+          throw new IllegalFormatWidthException(width)\n+        sendToDest(\"\\n\")\n \n-          def sciNotation(precision: Int) = {\n-            val exp = numberArg.toExponential(precision)\n-            with_+({\n-              // check if we need additional 0 padding in exponent\n-              // JavaDoc: at least 2 digits\n-              if ('e' == exp.charAt(exp.length - 3)) {\n-                exp.substring(0, exp.length - 1) + \"0\" +\n-                  exp.charAt(exp.length - 1)\n-              } else exp\n-            }, numberArg.isNaN || numberArg.isInfinite)\n-          }\n+      case _ =>\n+        throw new UnknownFormatConversionException(conversion.toString)\n+    }\n+  }\n+\n+  @inline private def validateFlags(flags: Flags, conversion: Char,\n+      invalidFlags: Int): Unit = {\n+\n+    @noinline def flagsConversionMismatch(): Nothing = {\n+      throw new FormatFlagsConversionMismatchException(\n+          flagsToString(new Flags(flags.bits & invalidFlags)), conversion)\n+    }\n+\n+    if ((flags.bits & invalidFlags) != 0)\n+      flagsConversionMismatch()\n+\n+    @noinline def illegalFlags(): Nothing =\n+      throw new IllegalFormatFlagsException(flagsToString(flags))\n+\n+    /* The test `(invalidFlags & BadCombo) == 0` is redundant, but is\n+     * constant-folded away at called site, and if false it allows to dce the\n+     * test after the `&&`. If both tests are eliminated, the entire `if`\n+     * disappears.\n+     */\n+    val BadCombo1 = LeftAlign | ZeroPad\n+    val BadCombo2 = PositivePlus | PositiveSpace\n+    if (((invalidFlags & BadCombo1) == 0 && (flags.bits & BadCombo1) == BadCombo1) ||\n+        ((invalidFlags & BadCombo2) == 0 && (flags.bits & BadCombo2) == BadCombo2)) {\n+      illegalFlags()\n+    }\n+  }\n+\n+  @inline private def validateFlagsForPercentAndNewline(flags: Flags,\n+      conversion: Char, invalidFlags: Int): Unit = {\n+\n+    @noinline def illegalFlags(): Nothing =\n+      throw new IllegalFormatFlagsException(flagsToString(flags))\n+\n+    if ((flags.bits & invalidFlags) != 0)\n+      illegalFlags()\n+  }\n+\n+  private def flagsToString(flags: Flags): String = {\n+    (if (flags.leftAlign) \"-\" else \"\") +\n+    (if (flags.altFormat) \"#\" else \"\") +\n+    (if (flags.positivePlus) \"+\" else \"\") +\n+    (if (flags.positiveSpace) \" \" else \"\") +\n+    (if (flags.zeroPad) \"0\" else \"\") +\n+    (if (flags.useGroupingSeps) \",\" else \"\") +\n+    (if (flags.negativeParen) \"(\" else \"\") +\n+    (if (flags.useLastIndex) \"<\" else \"\")\n+  }\n+\n+  private def computerizedScientificNotation(x: Double, precision: Int,\n+      forceDecimalSep: Boolean): String = {","path":"javalib/src/main/scala/java/util/Formatter.scala","position":639,"original_position":639,"commit_id":"5bb66462d631b81692bd0f3c0b00bf30ca3f0d9e","original_commit_id":"5bb66462d631b81692bd0f3c0b00bf30ca3f0d9e","user":{"login":"gzm0","id":1140323,"node_id":"MDQ6VXNlcjExNDAzMjM=","avatar_url":"https://avatars3.githubusercontent.com/u/1140323?v=4","gravatar_id":"","url":"https://api.github.com/users/gzm0","html_url":"https://github.com/gzm0","followers_url":"https://api.github.com/users/gzm0/followers","following_url":"https://api.github.com/users/gzm0/following{/other_user}","gists_url":"https://api.github.com/users/gzm0/gists{/gist_id}","starred_url":"https://api.github.com/users/gzm0/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gzm0/subscriptions","organizations_url":"https://api.github.com/users/gzm0/orgs","repos_url":"https://api.github.com/users/gzm0/repos","events_url":"https://api.github.com/users/gzm0/events{/privacy}","received_events_url":"https://api.github.com/users/gzm0/received_events","type":"User","site_admin":false},"body":"call this `altFormat`? Otherwise the logic that `altFormat` means `forceDecimalSep` for efg is very implicit.","created_at":"2018-06-17T14:31:02Z","updated_at":"2018-06-17T14:33:16Z","html_url":"https://github.com/scala-js/scala-js/pull/3380#discussion_r195931497","pull_request_url":"https://api.github.com/repos/scala-js/scala-js/pulls/3380","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/scala-js/scala-js/pulls/comments/195931497"},"html":{"href":"https://github.com/scala-js/scala-js/pull/3380#discussion_r195931497"},"pull_request":{"href":"https://api.github.com/repos/scala-js/scala-js/pulls/3380"}}},"pull_request":{"url":"https://api.github.com/repos/scala-js/scala-js/pulls/3380","id":195203144,"node_id":"MDExOlB1bGxSZXF1ZXN0MTk1MjAzMTQ0","html_url":"https://github.com/scala-js/scala-js/pull/3380","diff_url":"https://github.com/scala-js/scala-js/pull/3380.diff","patch_url":"https://github.com/scala-js/scala-js/pull/3380.patch","issue_url":"https://api.github.com/repos/scala-js/scala-js/issues/3380","number":3380,"state":"open","locked":false,"title":"Complete rewrite of java.util.Formatter.","user":{"login":"sjrd","id":535934,"node_id":"MDQ6VXNlcjUzNTkzNA==","avatar_url":"https://avatars3.githubusercontent.com/u/535934?v=4","gravatar_id":"","url":"https://api.github.com/users/sjrd","html_url":"https://github.com/sjrd","followers_url":"https://api.github.com/users/sjrd/followers","following_url":"https://api.github.com/users/sjrd/following{/other_user}","gists_url":"https://api.github.com/users/sjrd/gists{/gist_id}","starred_url":"https://api.github.com/users/sjrd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sjrd/subscriptions","organizations_url":"https://api.github.com/users/sjrd/orgs","repos_url":"https://api.github.com/users/sjrd/repos","events_url":"https://api.github.com/users/sjrd/events{/privacy}","received_events_url":"https://api.github.com/users/sjrd/received_events","type":"User","site_admin":false},"body":"This class was written a very long time ago, when quality standards were not as high as they are now. In this commit, we entirely rewrite it while aiming for:\r\n\r\n* Readability\r\n* Performance\r\n* Fidelity in a lot of corner cases wrt. the specification on the JVM, in particular in error cases\r\n\r\nThe corresponding test suite is significantly enhanced.\r\n\r\nIn the process, this commit fixes #1619 (thousands grouping), as well as many other issues, among which:\r\n\r\n* Formatting of astral characters with `%c`.\r\n* Formatting of `null` with all the format specifiers.\r\n* Handling of precision in non-numeric strings (basically `substring`).\r\n* Capturing `IOException`s thrown by the target `Appendable`.\r\n\r\nDespite the large number of fixes, the following items are still not addressed:\r\n\r\n* #3202 and, more generally, various discrepancies between the formatting of floating point numbers wrt. the specification, with `%f`, `%e` and `%g`.\r\n* Hexadecimal format for floating point numbers with `%a`.\r\n* Formatting of date/times with the `%t.` family.\r\n* Formatting of `BigInteger`s and `BigDecimal`s.","created_at":"2018-06-15T15:05:01Z","updated_at":"2018-06-17T14:33:16Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4afb5493f429354a902f96b0e5762e5d6c1250d2","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/scala-js/scala-js/pulls/3380/commits","review_comments_url":"https://api.github.com/repos/scala-js/scala-js/pulls/3380/comments","review_comment_url":"https://api.github.com/repos/scala-js/scala-js/pulls/comments{/number}","comments_url":"https://api.github.com/repos/scala-js/scala-js/issues/3380/comments","statuses_url":"https://api.github.com/repos/scala-js/scala-js/statuses/5bb66462d631b81692bd0f3c0b00bf30ca3f0d9e","head":{"label":"sjrd:fix-formatter","ref":"fix-formatter","sha":"5bb66462d631b81692bd0f3c0b00bf30ca3f0d9e","user":{"login":"sjrd","id":535934,"node_id":"MDQ6VXNlcjUzNTkzNA==","avatar_url":"https://avatars3.githubusercontent.com/u/535934?v=4","gravatar_id":"","url":"https://api.github.com/users/sjrd","html_url":"https://github.com/sjrd","followers_url":"https://api.github.com/users/sjrd/followers","following_url":"https://api.github.com/users/sjrd/following{/other_user}","gists_url":"https://api.github.com/users/sjrd/gists{/gist_id}","starred_url":"https://api.github.com/users/sjrd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sjrd/subscriptions","organizations_url":"https://api.github.com/users/sjrd/orgs","repos_url":"https://api.github.com/users/sjrd/repos","events_url":"https://api.github.com/users/sjrd/events{/privacy}","received_events_url":"https://api.github.com/users/sjrd/received_events","type":"User","site_admin":false},"repo":{"id":8660284,"node_id":"MDEwOlJlcG9zaXRvcnk4NjYwMjg0","name":"scala-js","full_name":"sjrd/scala-js","owner":{"login":"sjrd","id":535934,"node_id":"MDQ6VXNlcjUzNTkzNA==","avatar_url":"https://avatars3.githubusercontent.com/u/535934?v=4","gravatar_id":"","url":"https://api.github.com/users/sjrd","html_url":"https://github.com/sjrd","followers_url":"https://api.github.com/users/sjrd/followers","following_url":"https://api.github.com/users/sjrd/following{/other_user}","gists_url":"https://api.github.com/users/sjrd/gists{/gist_id}","starred_url":"https://api.github.com/users/sjrd/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sjrd/subscriptions","organizations_url":"https://api.github.com/users/sjrd/orgs","repos_url":"https://api.github.com/users/sjrd/repos","events_url":"https://api.github.com/users/sjrd/events{/privacy}","received_events_url":"https://api.github.com/users/sjrd/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/sjrd/scala-js","description":"Scala.js, the Scala to JavaScript compiler","fork":true,"url":"https://api.github.com/repos/sjrd/scala-js","forks_url":"https://api.github.com/repos/sjrd/scala-js/forks","keys_url":"https://api.github.com/repos/sjrd/scala-js/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sjrd/scala-js/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sjrd/scala-js/teams","hooks_url":"https://api.github.com/repos/sjrd/scala-js/hooks","issue_events_url":"https://api.github.com/repos/sjrd/scala-js/issues/events{/number}","events_url":"https://api.github.com/repos/sjrd/scala-js/events","assignees_url":"https://api.github.com/repos/sjrd/scala-js/assignees{/user}","branches_url":"https://api.github.com/repos/sjrd/scala-js/branches{/branch}","tags_url":"https://api.github.com/repos/sjrd/scala-js/tags","blobs_url":"https://api.github.com/repos/sjrd/scala-js/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sjrd/scala-js/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sjrd/scala-js/git/refs{/sha}","trees_url":"https://api.github.com/repos/sjrd/scala-js/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sjrd/scala-js/statuses/{sha}","languages_url":"https://api.github.com/repos/sjrd/scala-js/languages","stargazers_url":"https://api.github.com/repos/sjrd/scala-js/stargazers","contributors_url":"https://api.github.com/repos/sjrd/scala-js/contributors","subscribers_url":"https://api.github.com/repos/sjrd/scala-js/subscribers","subscription_url":"https://api.github.com/repos/sjrd/scala-js/subscription","commits_url":"https://api.github.com/repos/sjrd/scala-js/commits{/sha}","git_commits_url":"https://api.github.com/repos/sjrd/scala-js/git/commits{/sha}","comments_url":"https://api.github.com/repos/sjrd/scala-js/comments{/number}","issue_comment_url":"https://api.github.com/repos/sjrd/scala-js/issues/comments{/number}","contents_url":"https://api.github.com/repos/sjrd/scala-js/contents/{+path}","compare_url":"https://api.github.com/repos/sjrd/scala-js/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sjrd/scala-js/merges","archive_url":"https://api.github.com/repos/sjrd/scala-js/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sjrd/scala-js/downloads","issues_url":"https://api.github.com/repos/sjrd/scala-js/issues{/number}","pulls_url":"https://api.github.com/repos/sjrd/scala-js/pulls{/number}","milestones_url":"https://api.github.com/repos/sjrd/scala-js/milestones{/number}","notifications_url":"https://api.github.com/repos/sjrd/scala-js/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sjrd/scala-js/labels{/name}","releases_url":"https://api.github.com/repos/sjrd/scala-js/releases{/id}","deployments_url":"https://api.github.com/repos/sjrd/scala-js/deployments","created_at":"2013-03-08T21:32:59Z","updated_at":"2018-06-12T12:49:41Z","pushed_at":"2018-06-17T12:26:23Z","git_url":"git://github.com/sjrd/scala-js.git","ssh_url":"git@github.com:sjrd/scala-js.git","clone_url":"https://github.com/sjrd/scala-js.git","svn_url":"https://github.com/sjrd/scala-js","homepage":"https://www.scala-js.org/","size":18102,"stargazers_count":13,"watchers_count":13,"language":"Scala","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":1,"open_issues":0,"watchers":13,"default_branch":"master"}},"base":{"label":"scala-js:0.6.x","ref":"0.6.x","sha":"a8da4941af6a78b8b018fd0c0eeaabeaf8472ec5","user":{"login":"scala-js","id":6039724,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYwMzk3MjQ=","avatar_url":"https://avatars3.githubusercontent.com/u/6039724?v=4","gravatar_id":"","url":"https://api.github.com/users/scala-js","html_url":"https://github.com/scala-js","followers_url":"https://api.github.com/users/scala-js/followers","following_url":"https://api.github.com/users/scala-js/following{/other_user}","gists_url":"https://api.github.com/users/scala-js/gists{/gist_id}","starred_url":"https://api.github.com/users/scala-js/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scala-js/subscriptions","organizations_url":"https://api.github.com/users/scala-js/orgs","repos_url":"https://api.github.com/users/scala-js/repos","events_url":"https://api.github.com/users/scala-js/events{/privacy}","received_events_url":"https://api.github.com/users/scala-js/received_events","type":"Organization","site_admin":false},"repo":{"id":8272315,"node_id":"MDEwOlJlcG9zaXRvcnk4MjcyMzE1","name":"scala-js","full_name":"scala-js/scala-js","owner":{"login":"scala-js","id":6039724,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYwMzk3MjQ=","avatar_url":"https://avatars3.githubusercontent.com/u/6039724?v=4","gravatar_id":"","url":"https://api.github.com/users/scala-js","html_url":"https://github.com/scala-js","followers_url":"https://api.github.com/users/scala-js/followers","following_url":"https://api.github.com/users/scala-js/following{/other_user}","gists_url":"https://api.github.com/users/scala-js/gists{/gist_id}","starred_url":"https://api.github.com/users/scala-js/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/scala-js/subscriptions","organizations_url":"https://api.github.com/users/scala-js/orgs","repos_url":"https://api.github.com/users/scala-js/repos","events_url":"https://api.github.com/users/scala-js/events{/privacy}","received_events_url":"https://api.github.com/users/scala-js/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/scala-js/scala-js","description":"Scala.js, the Scala to JavaScript compiler","fork":false,"url":"https://api.github.com/repos/scala-js/scala-js","forks_url":"https://api.github.com/repos/scala-js/scala-js/forks","keys_url":"https://api.github.com/repos/scala-js/scala-js/keys{/key_id}","collaborators_url":"https://api.github.com/repos/scala-js/scala-js/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/scala-js/scala-js/teams","hooks_url":"https://api.github.com/repos/scala-js/scala-js/hooks","issue_events_url":"https://api.github.com/repos/scala-js/scala-js/issues/events{/number}","events_url":"https://api.github.com/repos/scala-js/scala-js/events","assignees_url":"https://api.github.com/repos/scala-js/scala-js/assignees{/user}","branches_url":"https://api.github.com/repos/scala-js/scala-js/branches{/branch}","tags_url":"https://api.github.com/repos/scala-js/scala-js/tags","blobs_url":"https://api.github.com/repos/scala-js/scala-js/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/scala-js/scala-js/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/scala-js/scala-js/git/refs{/sha}","trees_url":"https://api.github.com/repos/scala-js/scala-js/git/trees{/sha}","statuses_url":"https://api.github.com/repos/scala-js/scala-js/statuses/{sha}","languages_url":"https://api.github.com/repos/scala-js/scala-js/languages","stargazers_url":"https://api.github.com/repos/scala-js/scala-js/stargazers","contributors_url":"https://api.github.com/repos/scala-js/scala-js/contributors","subscribers_url":"https://api.github.com/repos/scala-js/scala-js/subscribers","subscription_url":"https://api.github.com/repos/scala-js/scala-js/subscription","commits_url":"https://api.github.com/repos/scala-js/scala-js/commits{/sha}","git_commits_url":"https://api.github.com/repos/scala-js/scala-js/git/commits{/sha}","comments_url":"https://api.github.com/repos/scala-js/scala-js/comments{/number}","issue_comment_url":"https://api.github.com/repos/scala-js/scala-js/issues/comments{/number}","contents_url":"https://api.github.com/repos/scala-js/scala-js/contents/{+path}","compare_url":"https://api.github.com/repos/scala-js/scala-js/compare/{base}...{head}","merges_url":"https://api.github.com/repos/scala-js/scala-js/merges","archive_url":"https://api.github.com/repos/scala-js/scala-js/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/scala-js/scala-js/downloads","issues_url":"https://api.github.com/repos/scala-js/scala-js/issues{/number}","pulls_url":"https://api.github.com/repos/scala-js/scala-js/pulls{/number}","milestones_url":"https://api.github.com/repos/scala-js/scala-js/milestones{/number}","notifications_url":"https://api.github.com/repos/scala-js/scala-js/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/scala-js/scala-js/labels{/name}","releases_url":"https://api.github.com/repos/scala-js/scala-js/releases{/id}","deployments_url":"https://api.github.com/repos/scala-js/scala-js/deployments","created_at":"2013-02-18T16:33:55Z","updated_at":"2018-06-17T09:28:13Z","pushed_at":"2018-06-17T12:27:42Z","git_url":"git://github.com/scala-js/scala-js.git","ssh_url":"git@github.com:scala-js/scala-js.git","clone_url":"https://github.com/scala-js/scala-js.git","svn_url":"https://github.com/scala-js/scala-js","homepage":"https://www.scala-js.org/","size":17873,"stargazers_count":3388,"watchers_count":3388,"language":"Scala","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":296,"mirror_url":null,"archived":false,"open_issues_count":68,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":296,"open_issues":68,"watchers":3388,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/scala-js/scala-js/pulls/3380"},"html":{"href":"https://github.com/scala-js/scala-js/pull/3380"},"issue":{"href":"https://api.github.com/repos/scala-js/scala-js/issues/3380"},"comments":{"href":"https://api.github.com/repos/scala-js/scala-js/issues/3380/comments"},"review_comments":{"href":"https://api.github.com/repos/scala-js/scala-js/pulls/3380/comments"},"review_comment":{"href":"https://api.github.com/repos/scala-js/scala-js/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/scala-js/scala-js/pulls/3380/commits"},"statuses":{"href":"https://api.github.com/repos/scala-js/scala-js/statuses/5bb66462d631b81692bd0f3c0b00bf30ca3f0d9e"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"gzm0"}}