Coder Social home page Coder Social logo

ts-reset's Introduction

ts-reset

TypeScript's built-in typings are not perfect. ts-reset makes them better.

Without ts-reset:

  • ๐Ÿšจ .json (in fetch) and JSON.parse both return any
  • ๐Ÿคฆ .filter(Boolean) doesn't behave how you expect
  • ๐Ÿ˜ก array.includes often breaks on readonly arrays

ts-reset smooths over these hard edges, just like a CSS reset does in the browser.

With ts-reset:

  • ๐Ÿ‘ .json (in fetch) and JSON.parse both return unknown
  • โœ… .filter(Boolean) behaves EXACTLY how you expect
  • ๐Ÿฅน array.includes is widened to be more ergonomic
  • ๐Ÿš€ And several more changes!

Example

// Import in a single file, then across your whole project...
import "@total-typescript/ts-reset";

// .filter just got smarter!
const filteredArray = [1, 2, undefined].filter(Boolean); // number[]

// Get rid of the any's in JSON.parse and fetch
const result = JSON.parse("{}"); // unknown

fetch("/")
  .then((res) => res.json())
  .then((json) => {
    console.log(json); // unknown
  });

Get Started

  1. Install: npm i -D @total-typescript/ts-reset

  2. Import once into any .ts or .tsx file:

import "@total-typescript/ts-reset";
  1. Enjoy improved typings across your entire project.

Installing only certain rules

By importing from @total-typescript/ts-reset, you're bundling all the recommended rules.

To only import the rules you want, you can import like so:

// Makes JSON.parse return unknown
import "@total-typescript/ts-reset/json-parse";

// Makes await fetch().then(res => res.json()) return unknown
import "@total-typescript/ts-reset/fetch";

Below is a full list of all the rules available.

Caveats

Use ts-reset in applications, not libraries

ts-reset is designed to be used in application code, not library code. Each rule you include will make changes to the global scope. That means that, simply by importing your library, your user will be unknowingly opting in to ts-reset.

Rules

Make JSON.parse return unknown

import "@total-typescript/ts-reset/json-parse";

JSON.parse returning any can cause nasty, subtle bugs. Frankly, any any's can cause bugs because they disable typechecking on the values they describe.

// BEFORE
const result = JSON.parse("{}"); // any

By changing the result of JSON.parse to unknown, we're now forced to either validate the unknown to ensure it's the correct type (perhaps using zod), or cast it with as.

// AFTER
import "@total-typescript/ts-reset/json-parse";

const result = JSON.parse("{}"); // unknown

Make .json() return unknown

import "@total-typescript/ts-reset/fetch";

Just like JSON.parse, .json() returning any introduces unwanted any's into your application code.

// BEFORE
fetch("/")
  .then((res) => res.json())
  .then((json) => {
    console.log(json); // any
  });

By forcing res.json to return unknown, we're encouraged to distrust its results, making us more likely to validate the results of fetch.

// AFTER
import "@total-typescript/ts-reset/fetch";

fetch("/")
  .then((res) => res.json())
  .then((json) => {
    console.log(json); // unknown
  });

Make .filter(Boolean) filter out falsy values

import "@total-typescript/ts-reset/filter-boolean";

The default behaviour of .filter can feel pretty frustrating. Given the code below:

// BEFORE
const filteredArray = [1, 2, undefined].filter(Boolean); // (number | undefined)[]

It feels natural that TypeScript should understand that you've filtered out the undefined from filteredArray. You can make this work, but you need to mark it as a type predicate:

const filteredArray = [1, 2, undefined].filter((item): item is number => {
  return !!item;
}); // number[]

Using .filter(Boolean) is a really common shorthand for this. So, this rule makes it so .filter(Boolean) acts like a type predicate on the array passed in, removing any falsy values from the array member.

// AFTER
import "@total-typescript/ts-reset/filter-boolean";

const filteredArray = [1, 2, undefined].filter(Boolean); // number[]

Make .includes on as const arrays less strict

import "@total-typescript/ts-reset/array-includes";

This rule improves on TypeScript's default .includes behaviour. Without this rule enabled, the argument passed to .includes MUST be a member of the array it's being tested against.

// BEFORE
const users = ["matt", "sofia", "waqas"] as const;

// Argument of type '"bryan"' is not assignable to
// parameter of type '"matt" | "sofia" | "waqas"'.
users.includes("bryan");

This can often feel extremely awkward. But with the rule enabled, .includes now takes a widened version of the literals in the const array.

// AFTER
import "@total-typescript/ts-reset/array-includes";

const users = ["matt", "sofia", "waqas"] as const;

// .includes now takes a string as the first parameter
users.includes("bryan");

This means you can test non-members of the array safely.

It also makes .includes a type predicate, meaning you can use it to narrow wider types to a set enum:

import "@total-typescript/ts-reset/array-includes";

const users = ["matt", "sofia", "waqas"] as const;

const isUser = (input: string) => {
  if (users.includes(input)) {
    // input is narrowed to "matt" | "sofia" | "waqas"
    console.log(input);
  }
};

Make Set.has() less strict

import "@total-typescript/ts-reset/set-has";

Similar to .includes, Set.has() doesn't let you pass members that don't exist in the set:

// BEFORE
const userSet = new Set(["matt", "sofia", "waqas"] as const);

// Argument of type '"bryan"' is not assignable to
// parameter of type '"matt" | "sofia" | "waqas"'.
userSet.has("bryan");

With the rule enabled, Set is much smarter:

// AFTER
import "@total-typescript/ts-reset/set-has";

const userSet = new Set(["matt", "sofia", "waqas"] as const);

// .has now takes a string as the argument!
userSet.has("bryan");

Removing any[] from Array.isArray()

import "@total-typescript/ts-reset/is-array";

When you're using Array.isArray, you can introduce subtle any's into your app's code.

// BEFORE

const validate = (input: unknown) => {
  if (Array.isArray(input)) {
    console.log(input); // any[]
  }
};

With is-array enabled, this check will now mark the value as unknown[]:

// AFTER
import "@total-typescript/ts-reset/is-array";

const validate = (input: unknown) => {
  if (Array.isArray(input)) {
    console.log(input); // unknown[]
  }
};

Rules we won't add

Object.keys/Object.entries

A common ask is to provide 'better' typings for Object.keys, so that it returns Array<keyof T> instead of Array<string>. Same for Object.entries. ts-reset won't be including rules to change this.

TypeScript is a structural typing system. One of the effects of this is that TypeScript can't always guarantee that your object types don't contain excess properties:

type Func = () => {
  id: string;
};

const func: Func = () => {
  return {
    id: "123",
    // No error on an excess property!
    name: "Hello!",
  };
};

So, the only reasonable type for Object.keys to return is Array<string>.

Generics for JSON.parse, Response.json etc

A common request is for ts-reset to add type arguments to functions like JSON.parse:

const str = JSON.parse<string>('"hello"');

console.log(str); // string

This appears to improve the DX by giving you autocomplete on the thing that gets returned from JSON.parse.

However, we argue that this is a lie to the compiler and so, unsafe.

JSON.parse and fetch represent validation boundaries - places where unknown data can enter your application code.

If you really know what data is coming back from a JSON.parse, then an as assertion feels like the right call:

const str = JSON.parse('"hello"') as string;

console.log(str); // string

This provides the types you intend and also signals to the developer that this is slightly unsafe.

ts-reset's People

Contributors

donavon avatar github-actions[bot] avatar mattpocock avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.