microsoft / microsoft/TypeScript

Smarter error messages for JSX accidentally put outside a JSX expression

Ouverte
#28,943 3 commentaires 2 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Domain: Error Messages In Discussion Suggestion
Langage dominant
Go
Étoiles
111k
Forks
14.3k
Merge moyen
2 j 4 h
PR mergées (30 j)
132

Description

Search Terms

TS2657 TS2695 TS2304 JSX map label:"Domain: JSX/TSX"

Suggestion

TS should emit clearer error messages for invalid JSX caused by the case where a developer expected code to be in a JSX expression context but instead it's evaluated in a non-JSX context (regular code). A signpost for these kinds of issues is that the compiler errors go away if the offending code is wrapped in a Fragment.

Use Cases

TS emits a helpful error message ([ts] JSX expressions must have one parent element. [2657]) when a simple JSX expression has two sibling elements without a single parent element or fragment. But this only works if your code successfully emits multiple JSX elements. If you're combining JSX and text e.g. <Foo>{bar} then the helpful error message is not shown.

These kinds of errors are easy to solve in simple JSX but can be really challenging to spot when the offending code is in the middle of complex TS code, because it's not obvious that the problem is JSX-related instead of "something in my TS code is broken".

The root cause is a misunderstanding between the developer (who's assuming code to be in a JSX expression context) and the compiler (which knows that the code is NOT in a JSX expression context.

From the developer's point of view, the fix for these problems is simple: wrap the offending TS code in a Fragment or another element. This gave me an idea for how TS could perhaps show smarter error messages. Could the TS compiler recognize cases where the following two conditions are true?

  1. the developer may have expected code to be evaluated as a JSX expression, e.g. the code is inside a JSX element, or the code has a JSX element as a sibling.
  2. the compiler errors go away if the code is treated as a JSX expression instead of as regular TS code

If so, then the TS compiler could show an error message (e.g. "JSX expressions must be wrapped in a fragment or JSX element") at the start or end of the offending code. Even if there are false positives-- e.g. legitimate code bugs inside a {code} block inside some JSX-- it's probably better to give devs multiple possible reasons for a compiler error than to omit a likely reason.

For example, here's two patterns I've run across recently:

  • Pattern A: An inline function is defined inside a JSX element (or fragment) AND and there's a compiler error in that code AND the errors go away if the function's body were wrapped in a fragment. Examples:
    Test #1: GOOD
    <>
      {data.map((str: string, i: number) => (
        {str} // no error
      ))}
    </>

    Test #2: GOOD
    <>{data.map((str: string, i: number) => (
        <>{i}: {str}</> // no error
      ))}
    </>

    Test #3: BAD
    <>
      {data.map((str: string, i: number) => (
        {i}: {str} // errors: [ts] ')' expected. [1005]; [ts] Cannot find name 'str'. [2304]
      ))}
    </>
  • Pattern B: A JSX element (wrapped in < and >) is immediately preceded and/or immediately followed by non-JSX code that generates a compiler error AND the code would not throw a compiler error if it were wrapped in a Fragment.
    Test #4: GOOD 
    <>
      {data.map((str: string, i: number) => (
        {i}+": "+{str}+"\n" // no error
      ))}
    </>

    Test #5: GOOD
    <>
      {data.map((str: string, i: number) => (
        <br/><>{i}+": "+{str}+"\n"</> // error on <br/>: [ts] Left side of comma operator is unused and has no side effects. [2695]
      )) // Good error here: [ts] JSX expressions must have one parent element. [2657]
      }      
    </>

    Test #6: BAD
    <>
      {data.map((str: string, i: number) => (
        <br/>{i}+": "+{str}+"\n" // errors: [ts] Cannot find name 'i'. [2304]; [ts] Cannot find name 'str'. [2304]
      )) // error (on second parenthesis): [ts] '}' expected. [1005]
      }      
    </>

Examples

Here's the full example from above.

const repro = () => {

  const simpleJSX = (
    <div>hi</div>  // confusing error here: [ts] Left side of comma operator is unused and has no side effects. [2695]
    <div>bye</div>
  ); // Good error here: [ts] JSX expressions must have one parent element. [2657]
  
  const data = ['a', 'b', 'c'];
  return (
  <>
    Test #1: GOOD
    <>
      {data.map((str: string, i: number) => (
        {str} // no error
      ))}
    </>

    Test #2: GOOD
    <>{data.map((str: string, i: number) => (
        <>{i}: {str}</> // no error
      ))}
    </>

    Test #3: BAD
    <>
      {data.map((str: string, i: number) => (
        {i}: {str} // errors: [ts] ')' expected. [1005]; [ts] Cannot find name 'str'. [2304]
      ))}
    </>

    Test #4: GOOD 
    <>
      {data.map((str: string, i: number) => (
        {i}+": "+{str}+"\n" // no error
      ))}
    </>

    Test #5: GOOD
    <>
      {data.map((str: string, i: number) => (
        <br/><>{i}+": "+{str}+"\n"</> // error on <br/>: [ts] Left side of comma operator is unused and has no side effects. [2695]
      )) // Good error here: [ts] JSX expressions must have one parent element. [2657]
      }      
    </>

    Test #6: BAD
    <>
      {data.map((str: string, i: number) => (
        <br/>{i}+": "+{str}+"\n" // errors: [ts] Cannot find name 'i'. [2304]; [ts] Cannot find name 'str'. [2304]
      )) // error (on second parenthesis): [ts] '}' expected. [1005]
      }      
    </>

  </>
)};

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
    (because it only applies to code that already has a compiler error)

  • This wouldn't change the runtime behavior of existing JavaScript code

  • This could be implemented without emitting different JS based on the types of the expressions

  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)

  • This feature would agree with the rest of TypeScript's Design Goals.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par reproduire les exemples TSX de l’issue et comparer les diagnostics existants TS2657, TS2695, TS2304 et TS1005. C’est terminé lorsque le compilateur identifie les cas d’expressions JSX décrits avec des indications plus claires concernant les fragments ou les éléments, tout en préservant le comportement pour le code valide.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
typescript
Domaine
compilers
Type d'issue
Fonctionnalité
Difficulté
5/5
Temps estimé
Plus d'une semaine
Activité
À l'abandon
Clarté
À clarifier
Accessibilité débutants
28/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.