microsoft / microsoft/TypeScript

Smarter error messages for JSX accidentally put outside a JSX expression

Aperta
#28,943 3 commenti 2 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Domain: Error Messages In Discussion Suggestion
Lingua principale
Go
Stelle
111k
Fork
14.3k
Merge medio
2g 4h
PR unite (30g)
132

Descrizione

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.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia riproducendo gli esempi TSX nell’issue e confrontando le diagnostiche esistenti TS2657, TS2695, TS2304 e TS1005. Il lavoro è completato quando il compilatore identifica i casi di espressioni JSX descritti con indicazioni più chiare su frammenti o elementi, preservando al contempo il comportamento per il codice valido.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
typescript
Ambito
compilers
Tipo di issue
Funzionalità
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Ferma
Chiarezza
Da chiarire
Idoneità per principianti
28/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.