[react-devtools] Keep console specifiers literal when no argument is supplied (#36930)
`formatConsoleArguments` in `packages/react-devtools-shared/src/backend/utils/formatConsoleArguments.js` is used by the DevTools backend (via `hook.js`) to inline `console.*` printf-style substitutions after stripping React's appended component stack. For `%s`/`%d`/`%i`/`%f` it consumes the next argument with `args.splice(argumentsPointer, 1)` and formats the result. When a format string has more specifiers than arguments, `splice` returns an empty array, so `arg` is `undefined` and the specifier is rendered as text: `%s` becomes `"undefined"`, and `%d`/`%i`/`%f` become `"NaN"`. ```js formatConsoleArguments('%s %s', 'the'); // before: ['the undefined'] // after: ['the %s'] ``` Browsers and Node's `util.format` leave an unmatched specifier as a literal (`console.log('%s %s', 'a')` prints `a %s`; `console.log('%d')` prints `%d`). So a message like `console.warn('value: %s')` was shown in DevTools as `value: undefined` instead of `value: %s`. This guards each of the `%d`/`%i`, `%f`, and `%s` cases on argument availability (`argumentsPointer >= args.length`): when nothing is left to consume it keeps the specifier text and does not splice, mirroring the existing trailing-`%` handling added in #36852. An explicitly passed `undefined`/`null` argument is unchanged and still renders as `undefined`/`null`, since a value is present at that position (the `formats nullish values` test still passes). ## How did you test this change? Added a regression test to the existing `formatConsoleArguments` describe block in `packages/react-devtools-shared/src/__tests__/utils-test.js`: ```js it('keeps specifiers literal when no argument is supplied', () => { expect(formatConsoleArguments('%s %s', 'the')).toEqual(['the %s']); expect(formatConsoleArguments('%s %d', 'value')).toEqual(['value %d']); expect(formatConsoleArguments('%s %i', 'value')).toEqual(['value %i']); expect(formatConsoleArguments('%s %f', 'value')).toEqual(['value %f']); }); ``` Each assertion fails on `main` (it produces `['the undefined']` and `['value NaN']`) and passes with the fix. Commands run locally: ``` yarn test --build --project devtools packages/react-devtools-shared/src/__tests__/utils-test.js # 51 passed, 51 total yarn lint packages/react-devtools-shared/src/backend/utils/formatConsoleArguments.js \ packages/react-devtools-shared/src/__tests__/utils-test.js # Lint passed. yarn flow dom-node # No errors! yarn prettier-check packages/react-devtools-shared/src/backend/utils/formatConsoleArguments.js \ packages/react-devtools-shared/src/__tests__/utils-test.js # clean ``` Cross-checked the expected output against Node `util.format`: `util.format('%s %s', 'the')` -> `the %s`; `util.format('%d')` -> `%d`.