Skip to content

Different output for optional call/member expression using typescript parser vs babel and babel-ts #15785

@faultyserver

Description

@faultyserver

Prettier 3.1.1
Playground link

--parser babel
--print-width 120

Input:

// CASE 1:
// Normal behavior, the `isEqual` call breaks.
function someFunctionName(
  someLongBreakingParameterName,
  anotherLongParameterName,
) {
  return isEqual(a.map(([t, _]) => t?.id), b.map(([t, _]) => t?.id));
}

// When access to `a` and `b` is optional, typescript doesn't break
// the call, but babel and babel-ts do.
function someFunctionName(
  someLongBreakingParameterName,
  anotherLongParameterName,
) {
  return isEqual(a?.map(([t, _]) => t?.id), b?.map(([t, _]) => t?.id));
}

// CASE 2:
// Normal behavior, the `filter` call uses a grouped layout (puts the
// arrow expression parameters on the same line as "filter").
const theValue = Object.entries(someLongObjectName).filter(
  ([listingId]) => someListToCompareToHere.includes(listingId),
);

// With an optional member expression, babel-ts keeps this as a grouped
// layout, but typescript ungroups it onto a new line.
const theValue = Object.entries(someLongObjectName).filter(
  ([listingId]) => someListToCompareToHere?.includes(listingId),
);

// CASE 3:
// Normal behavior, the template expression breaks at the first interpolation.
logger.log(
  `A long template string with a conditional: ${channel.id}, and then some more content that continues until ${JSON.stringify(location)}`
);

// With an optional member expression, typescript doesn't break the first
// interpolation and instead breaks the second.
logger.log(
  `A long template string with a conditional: ${channel?.id}, and then some more content that continues until ${JSON.stringify(location)}`
);

Output:

// CASE 1:
// Normal behavior, the `isEqual` call breaks.
function someFunctionName(someLongBreakingParameterName, anotherLongParameterName) {
  return isEqual(
    a.map(([t, _]) => t?.id),
    b.map(([t, _]) => t?.id),
  );
}

// When access to `a` and `b` is optional, typescript doesn't break
// the call, but babel and babel-ts do.
function someFunctionName(someLongBreakingParameterName, anotherLongParameterName) {
  return isEqual(
    a?.map(([t, _]) => t?.id),
    b?.map(([t, _]) => t?.id),
  );
}

// CASE 2:
// Normal behavior, the `filter` call uses a grouped layout (puts the
// arrow expression parameters on the same line as "filter").
const theValue = Object.entries(someLongObjectName).filter(([listingId]) =>
  someListToCompareToHere.includes(listingId),
);

// With an optional member expression, babel-ts keeps this as a grouped
// layout, but typescript ungroups it onto a new line.
const theValue = Object.entries(someLongObjectName).filter(([listingId]) =>
  someListToCompareToHere?.includes(listingId),
);

// CASE 3:
// Normal behavior, the template expression breaks at the first interpolation.
logger.log(
  `A long template string with a conditional: ${
    channel.id
  }, and then some more content that continues until ${JSON.stringify(location)}`,
);

// With an optional member expression, typescript doesn't break the first
// interpolation and instead breaks the second.
logger.log(
  `A long template string with a conditional: ${
    channel?.id
  }, and then some more content that continues until ${JSON.stringify(location)}`,
);

Expected behavior:

The output from each of these 3 cases should match regardless of which parser is used (babel, babel-ts, or typescript).

The consistent difference here is whether the expression is optional or not. Inspecting the parsed ASTs, Babel (and thus babel-ts) parses these as distinct node types (OptionalCallExpression and OptionalMemberExpression), but the TypeScript parser just uses the plain CallExpression and MemberExpression nodes with an extra optional property on them.

From #10244, it seems like maybe in the past (or in a different situation), these were parsed as ChainExpression, which then got transformed by Prettier into the appropriate optional expression nodes, but that is no longer the case, so the transform doesn't pick them up and convert as needed.

Metadata

Metadata

Assignees

Labels

help wantedWe're a small group who can't get to every issue promptly. We’d appreciate help fixing this issue!locked-due-to-inactivityPlease open a new issue and fill out the template instead of commenting.

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions