@@ -15,6 +15,8 @@ const astUtils = require("./utils/ast-utils");
1515//------------------------------------------------------------------------------
1616
1717/** @typedef {import("estree").Node } ASTNode */
18+ /** @typedef {import("eslint").Rule.Fix } Fix */
19+ /** @typedef {import("eslint").Rule.RuleFixer } RuleFixer */
1820
1921//------------------------------------------------------------------------------
2022// Helpers
@@ -309,21 +311,19 @@ module.exports = {
309311 }
310312
311313 /**
312- * Gets the token after which new arguments should be inserted for the
313- * given argument, accounting for parentheses wrapping the argument so
314- * that insertions land outside them (e.g. `new Error(("msg"))`).
315- * @param {ASTNode } argNode The argument node.
316- * @param {ASTNode } callNode The enclosing call or new expression.
317- * @returns {Token } The token to insert after.
314+ * Finds the first token that isn't followed by a closing parenthesis in a specified range.
315+ * This is the token after which new arguments should be inserted.
316+ * @param {Token } firstToken The first token to start searching from.
317+ * @param {Token } lastToken The last token to scan up to (inclusive).
318+ * @returns {Token } The first token that isn't followed by a closing parenthesis, or `lastToken` if none is found.
318319 */
319- function getLastArgumentToken ( argNode , callNode ) {
320- const callLastToken = sourceCode . getLastToken ( callNode ) ;
321- let token = sourceCode . getLastToken ( argNode ) ;
320+ function findInsertionTokenAfterParens ( firstToken , lastToken ) {
321+ const lastIndex = lastToken . range [ 1 ] ;
322+ let token = firstToken ;
322323 let nextToken = sourceCode . getTokenAfter ( token ) ;
323324
324325 while (
325- nextToken &&
326- nextToken !== callLastToken &&
326+ nextToken . range [ 1 ] <= lastIndex &&
327327 astUtils . isClosingParenToken ( nextToken )
328328 ) {
329329 token = nextToken ;
@@ -332,6 +332,61 @@ module.exports = {
332332 return token ;
333333 }
334334
335+ /**
336+ * Adds arguments for a call/new expression with no arguments.
337+ * Works for `new Error`, `new (Error)`, and forms that already include empty argument parentheses.
338+ * @param {RuleFixer } fixer The fixer instance.
339+ * @param {ASTNode & { callee: ASTNode } } throwExpression The thrown CallExpression or NewExpression node.
340+ * @param {string } text The arguments to insert.
341+ * @returns {Fix } The fixer operation.
342+ */
343+ function addArgumentsToEmptyCall ( fixer , throwExpression , text ) {
344+ const callClosingParenToken =
345+ sourceCode . getLastToken ( throwExpression ) ;
346+ const lastCalleeToken = sourceCode . getLastToken (
347+ throwExpression . callee ,
348+ ) ;
349+ const parenToken = sourceCode . getFirstTokenBetween (
350+ lastCalleeToken ,
351+ callClosingParenToken ,
352+ astUtils . isOpeningParenToken ,
353+ ) ;
354+
355+ if ( parenToken ) {
356+ return fixer . insertTextAfter ( parenToken , text ) ;
357+ }
358+
359+ const insertionToken = findInsertionTokenAfterParens (
360+ lastCalleeToken ,
361+ callClosingParenToken ,
362+ ) ;
363+
364+ return fixer . insertTextAfter ( insertionToken , `(${ text } )` ) ;
365+ }
366+
367+ /**
368+ * Appends additional arguments after the last argument of a call/new expression,
369+ * accounting for any wrapping parentheses around that argument.
370+ * @param {RuleFixer } fixer The fixer instance.
371+ * @param {ASTNode & { arguments: ASTNode[] } } throwExpression The thrown CallExpression or NewExpression node.
372+ * @param {string } text The additional arguments to insert, including the leading comma.
373+ * @returns {Fix } The fixer operation.
374+ */
375+ function appendArguments ( fixer , throwExpression , text ) {
376+ const lastArgument = throwExpression . arguments . at ( - 1 ) ;
377+ const lastArgumentToken = sourceCode . getLastToken ( lastArgument ) ;
378+ const lastTokenBeforeArgListParen = sourceCode . getLastToken (
379+ throwExpression ,
380+ { skip : 1 } ,
381+ ) ;
382+ const insertionToken = findInsertionTokenAfterParens (
383+ lastArgumentToken ,
384+ lastTokenBeforeArgListParen ,
385+ ) ;
386+
387+ return fixer . insertTextAfter ( insertionToken , text ) ;
388+ }
389+
335390 //----------------------------------------------------------------------
336391 // Public
337392 //----------------------------------------------------------------------
@@ -450,51 +505,27 @@ module.exports = {
450505
451506 if ( ! errorsArg ) {
452507 // Case: `throw new AggregateError()` → insert all arguments
453- const lastToken =
454- sourceCode . getLastToken (
455- throwExpression ,
456- ) ;
457- const lastCalleeToken =
458- sourceCode . getLastToken (
459- throwExpression . callee ,
460- ) ;
461- const parenToken =
462- sourceCode . getFirstTokenBetween (
463- lastCalleeToken ,
464- lastToken ,
465- astUtils . isOpeningParenToken ,
466- ) ;
467-
468- if ( parenToken ) {
469- return fixer . insertTextAfter (
470- parenToken ,
471- `[], "", { cause: ${ caughtError . name } }` ,
472- ) ;
473- }
474- return fixer . insertTextAfter (
475- throwExpression . callee ,
476- `([], "", { cause: ${ caughtError . name } })` ,
508+ return addArgumentsToEmptyCall (
509+ fixer ,
510+ throwExpression ,
511+ `[], "", { cause: ${ caughtError . name } }` ,
477512 ) ;
478513 }
479514
480515 if ( ! messageArg ) {
481516 // Case: `throw new AggregateError([])` → insert message and options
482- return fixer . insertTextAfter (
483- getLastArgumentToken (
484- errorsArg ,
485- throwExpression ,
486- ) ,
517+ return appendArguments (
518+ fixer ,
519+ throwExpression ,
487520 `, "", { cause: ${ caughtError . name } }` ,
488521 ) ;
489522 }
490523
491524 if ( ! optionsArg ) {
492525 // Case: `throw new AggregateError([], "")` → insert error options only
493- return fixer . insertTextAfter (
494- getLastArgumentToken (
495- messageArg ,
496- throwExpression ,
497- ) ,
526+ return appendArguments (
527+ fixer ,
528+ throwExpression ,
498529 `, { cause: ${ caughtError . name } }` ,
499530 ) ;
500531 }
@@ -511,39 +542,17 @@ module.exports = {
511542
512543 if ( ! messageArg ) {
513544 // Case: `throw new Error()` → insert both message and options
514- const lastToken =
515- sourceCode . getLastToken (
516- throwExpression ,
517- ) ;
518- const lastCalleeToken =
519- sourceCode . getLastToken (
520- throwExpression . callee ,
521- ) ;
522- const parenToken =
523- sourceCode . getFirstTokenBetween (
524- lastCalleeToken ,
525- lastToken ,
526- astUtils . isOpeningParenToken ,
527- ) ;
528-
529- if ( parenToken ) {
530- return fixer . insertTextAfter (
531- parenToken ,
532- `"", { cause: ${ caughtError . name } }` ,
533- ) ;
534- }
535- return fixer . insertTextAfter (
536- throwExpression . callee ,
537- `("", { cause: ${ caughtError . name } })` ,
545+ return addArgumentsToEmptyCall (
546+ fixer ,
547+ throwExpression ,
548+ `"", { cause: ${ caughtError . name } }` ,
538549 ) ;
539550 }
540551 if ( ! optionsArg ) {
541552 // Case: `throw new Error("Some message")` → insert only options
542- return fixer . insertTextAfter (
543- getLastArgumentToken (
544- messageArg ,
545- throwExpression ,
546- ) ,
553+ return appendArguments (
554+ fixer ,
555+ throwExpression ,
547556 `, { cause: ${ caughtError . name } }` ,
548557 ) ;
549558 }
@@ -569,40 +578,18 @@ module.exports = {
569578
570579 if ( lastProvidedArg ) {
571580 // Options slot missing, all prior args provided → append options
572- return fixer . insertTextAfter (
573- getLastArgumentToken (
574- lastProvidedArg ,
575- throwExpression ,
576- ) ,
581+ return appendArguments (
582+ fixer ,
583+ throwExpression ,
577584 `, { cause: ${ caughtError . name } }` ,
578585 ) ;
579586 }
580587
581- // argumentPosition: 1 and no args → insert `{ cause: err }` inside parens
582- const lastToken =
583- sourceCode . getLastToken (
584- throwExpression ,
585- ) ;
586- const lastCalleeToken =
587- sourceCode . getLastToken (
588- throwExpression . callee ,
589- ) ;
590- const parenToken =
591- sourceCode . getFirstTokenBetween (
592- lastCalleeToken ,
593- lastToken ,
594- astUtils . isOpeningParenToken ,
595- ) ;
596-
597- if ( parenToken ) {
598- return fixer . insertTextAfter (
599- parenToken ,
600- `{ cause: ${ caughtError . name } }` ,
601- ) ;
602- }
603- return fixer . insertTextAfter (
604- throwExpression . callee ,
605- `({ cause: ${ caughtError . name } })` ,
588+ // argumentPosition: 1 and no args → insert options inside parens
589+ return addArgumentsToEmptyCall (
590+ fixer ,
591+ throwExpression ,
592+ `{ cause: ${ caughtError . name } }` ,
606593 ) ;
607594 }
608595
0 commit comments