microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.04k stars 12.49k forks source link

more readable Error Message Format Proposal #49011

Open Ali-Hussein-dev opened 2 years ago

Ali-Hussein-dev commented 2 years ago

Suggestion

When TS compares two object types and objects that don't have a compatible type, TS shows often a messy message which makes things worse. When comparing two object types, I found there are three main categories why they are not compatible,

  1. missing properties,
  2. unknown properties,
  3. incompatible properties type based on that, TS can show much more readable error messages by pointing just to the case/s (missing, unknown, and incompatible properties) which cause the error.

📃 Motivating Example

Here is an example of what I think TS should show

interface FruitsCountT {
  orange: number
  apple: number
  banana: number 
}
// case 1
const groupOne: FruitsCountT= {
  orange:4,
  apple: 8,
}
// error message: `groupOne` is **missing** the  following properties `banana`

// case 2
const groupTwo: FruitsCountT= {
  orange:4, 
  banana: 8,
apples: 10
}
// error message: `groupTwo` has **unknown** properties `apples`, did you mean `apple`

// case 3
const groupThree: FruitsCountT= {
  orange:4,
  banana: "8",
  apple: "10", 
}

/**
 * Error message: the following properties don't have compatible type as expected, 
 * for `apple` I expect a number but you give me a string
 * for `banana` I expect a number but you give me a string
 */

When two or more cases happen at the same time, then a separate message for each case, the user should expect.

For nested objects, I think an object-property should be handled the same as if it is an independent object. here is an example of nested objects.

interface MenuT {
  id: string;
  drinks: {
    softdrinks: string[];
    juices: string[];
  };
  dishes: {
    pasta: string[];
    salads: string[];
  };
}
const menu: MenuT = {
  id: '1',
  drinks: {
    softdrinks: [],
 // missing 
  },
  dishes: {
    pasta: [{name:"Lasagna"}], // incompatible 
    salad: ["tuna salad"], // unknown
  },
};
/**
 * the object has three type errors: 
 * error message: `menu.dishes` has unknown properties `salad`, did you mean `salads`?
 * error message: `menu.drinks` is missing the following properties `juices` 
 * error message: `pasta` property type, I expect `string[]` but you give `[{name:"Lasagna"}]`
 */

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

✅ Viability Checklist

My suggestion meets these guidelines:

⭐ Suggestions

💻 Use Cases

What workarounds are you using in the meantime?

vscode extension ts-error-translator to make TS messages more readable.

RyanCavanaugh commented 2 years ago

I'm sort of confused, because this is what happens today already:

Missing property

Property 'banana' is missing in type '{ orange: number; apple: number; }' but required in type 'FruitsCountT'.

Excess misspelled property

Type '{ orange: number; banana: number; apples: number; }' is not assignable to type 'FruitsCountT'.
  Object literal may only specify known properties, but 'apples' does not exist in type 'FruitsCountT'. Did you mean to write 'apple'?

Type mismatch - the error spans are on the incorrect property names

  banana: "8",
  ~~~~~~
Type 'string' is not assignable to type 'number'.

Is your suggestion about the phrasing of the message?

fatcerberus commented 2 years ago

The suggestion seems to be to consolidate all such errors in a given object literal into a single “type error” diagnostic (with the offending properties listed), regardless of nesting level. At present, IIRC, a single object literal may produce multiple error diagnostics.

danfma commented 2 years ago

I'm sort of confused, because this is what happens today already:

Missing property

Property 'banana' is missing in type '{ orange: number; apple: number; }' but required in type 'FruitsCountT'.

Excess misspelled property

Type '{ orange: number; banana: number; apples: number; }' is not assignable to type 'FruitsCountT'.
  Object literal may only specify known properties, but 'apples' does not exist in type 'FruitsCountT'. Did you mean to write 'apple'?

Type mismatch - the error spans are on the incorrect property names

  banana: "8",
  ~~~~~~
Type 'string' is not assignable to type 'number'.

Is your suggestion about the phrasing of the message?

I've seen that before. When you have a complex composition, usually, it is almost impossible to find the error because you will have a lot of introspection that makes really hard to understand the reason for the error, even for people with a good understanding of the language.

Ali-Hussein-dev commented 2 years ago

@RyanCavanaugh I miss the clear distinction between expectation and what is given as type. I find the second message format shorter, clearer and it tells me where to go to fix it.

// ONE LEVEL OBJECTS:
// current message: Property 'banana' is missing in type '{ orange: number; apple: number; }' but required in type 'FruitsCountT'.
// error message: `groupOne` object is **missing** the  following properties `banana`

// NESTED OBJECTS:
// current message will tell me that is not what TS expects and it assumes where the offending properties come from. 
// error message: `lever1.lever2.level3` object is missing the following properties `a` `b` ...etc // the message tells me where to go (here is level1 is the root type)