Repository: mattpocock/ts-reset Branch: main Commit: 22f39a8da0b7 Files: 40 Total size: 29.9 KB Directory structure: gitextract_odp9pd_g/ ├── .changeset/ │ ├── README.md │ └── config.json ├── .github/ │ └── workflows/ │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── package.json ├── readme.md ├── scripts/ │ ├── build.ts │ └── lint.ts ├── src/ │ ├── entrypoints/ │ │ ├── array-includes.d.ts │ │ ├── array-index-of.d.ts │ │ ├── dom.d.ts │ │ ├── fetch.d.ts │ │ ├── filter-boolean.d.ts │ │ ├── is-array.d.ts │ │ ├── json-parse.d.ts │ │ ├── map-constructor.d.ts │ │ ├── map-has.d.ts │ │ ├── promise-catch.d.ts │ │ ├── recommended.d.ts │ │ ├── set-has.d.ts │ │ ├── storage.d.ts │ │ └── utils.d.ts │ └── tests/ │ ├── array-includes.ts │ ├── array-index-of.ts │ ├── fetch.ts │ ├── filter-boolean.ts │ ├── is-array.ts │ ├── json-parse.ts │ ├── map-constructor.ts │ ├── map-has.ts │ ├── promise-catch.ts │ ├── set-has.ts │ ├── storage.ts │ └── utils.ts ├── tsconfig.json └── turbo.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .changeset/README.md ================================================ # Changesets Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) ================================================ FILE: .changeset/config.json ================================================ { "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], "linked": [], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": [] } ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: push: branches: - "**" pull_request: branches: - "**" jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: 20.x cache: "pnpm" - run: pnpm install --frozen-lockfile - run: pnpm run ci ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: push: branches: - "main" concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: 20.x cache: "pnpm" - run: pnpm install --frozen-lockfile - name: Create Release Pull Request or Publish id: changesets uses: changesets/action@v1 with: publish: pnpm run release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .gitignore ================================================ node_modules dist .turbo ================================================ FILE: .npmignore ================================================ .changeset src scripts pnpm-lock.yaml tsconfig.json .gitignore .github .changeset .turbo turbo.json og-image.png ================================================ FILE: CHANGELOG.md ================================================ # @total-typescript/ts-reset ## 0.6.1 ### Patch Changes - 757be40: Fixed a bug where creating an empty map would no longer infer types correctly. ## 0.6.0 ### Minor Changes - 6574858: Added a rule, `/map-constructor`, to default `Map` to `Map` when no arguments are passed to the constructor. Before, you'd get `any` for both key and value types. Now, the result of `Map.get` is `unknown` instead of `any`: ```ts const userMap = new Map(); const value = userMap.get("matt"); // value: unknown ``` This now is part of the recommended rules. - 5bf3a15: Added a rule, `/promise-catch`, to change the `catch` method to take `unknown` instead of `any` as an argument. ```ts const promise = Promise.reject("error"); // BEFORE promise.catch((error) => { console.error(error); // error is any! }); // AFTER promise.catch((error) => { console.error(error); // error is unknown! }); ``` ### Patch Changes - 53cee4f: author: @none23 Fixed a bug where running .filter on a union of arrays would not work. ## 0.5.1 ### Patch Changes - Added homepage for npm purposes. ## 0.5.0 ### Minor Changes - 49b8603: Added a rule, `/session`, to make sessionStorage and localStorage safer. ```ts // Is now typed as `unknown`, not `any`! localStorage.a; // Is now typed as `unknown`, not `any`! sessionStorage.abc; ``` - 49b8603: Added a `/dom` entrypoint to allow users to import DOM-only rules. ## 0.4.1 ### Patch Changes - No changes, just pushing to fix the previous slightly borked release. ## 0.4.0 ### Minor Changes - ce9db42: Added support for widening in `Array.lastIndexOf`, `Array.indexOf`, `ReadonlyArray.lastIndexOf` and `ReadonlyArray.indexOf`. - 107dfc2: Changed the array.includes on readonly arrays to NOT be a type predicate. Before this change, this perfectly valid code would not behave correctly. ```ts type Code = 0 | 1 | 2; type SpecificCode = 0 | 1; const currentCode: Code = 0; // Create an empty list of subset type const specificCodeList: ReadonlyArray = []; // This will be false, since 0 is not in [] if (specificCodeList.includes(currentCode)) { currentCode; // -> SpecificCode } else { // This branch will be entered, and ts will think z is 2, when it is actually 0 currentCode; // -> 2 } ``` Removing the type predicate brings ts-reset closer towards correctness. - 4765413: author: @mefechoel Added the `Map.has` rule. Similar to `.includes` or `Set.has()`, `Map.has()` doesn't let you pass members that don't exist in the map's keys: ```ts // BEFORE const userMap = new Map([ ["matt", 0], ["sofia", 1], [2, "waqas"], ] as const); // Argument of type '"bryan"' is not assignable to // parameter of type '"matt" | "sofia" | "waqas"'. userMap.has("bryan"); ``` With the rule enabled, `Map` follows the same semantics as `Set`. ```ts // AFTER import "@total-typescript/ts-reset/map-has"; const userMap = new Map([ ["matt", 0], ["sofia", 1], [2, "waqas"], ] as const); // .has now takes a string as the argument! userMap.has("bryan"); ``` ### Patch Changes - b15aaa4: Fixed an oversight with the initial `set-has` implementation by adding support to `ReadonlySet`. ## 0.3.7 ### Patch Changes - Added license and switched to MIT ## 0.3.6 ### Patch Changes - d3ddefa: Changed the exports map so "types" appears top ## 0.3.5 ### Patch Changes - Another fix for deploy process. ## 0.3.4 ### Patch Changes - Fixed an issue where dist folder was not deployed. ## 0.3.3 ### Patch Changes - Fixed a bug where 0n was not being filtered out by filter-boolean ## 0.3.2 ### Patch Changes - Removed the ability to use Set.has as a type predicate. This ensures that Set.has never sets the checked element to never. ## 0.3.1 ### Patch Changes - Fixed a bug where Array.includes was returning a predicate, which gave false positives. ## 0.3.0 ### Minor Changes - ed9edc1: Added improved typings for Set.has ### Patch Changes - d27e819: Fixed issue where webpack wasn't recognizing the exports map ## 0.2.1 ### Patch Changes - Readme tweak ## 0.2.0 ### Minor Changes - e5a33c9: Added support for array.includes ### Patch Changes - 8fe8e29: Improved filter-boolean to handle falsy values, not just NonNullable values ## 0.1.4 ### Patch Changes - Fixed exports (finally) ## 0.1.3 ### Patch Changes - bb3f2d1: Attempted fix for exports maps ## 0.1.2 ### Patch Changes - 4cfb07e: Fixed build process and moved to .d.ts files ## 0.1.1 ### Patch Changes - 0360275: Fixed type imports - e62b05a: Added .npmignore ## 0.1.0 ### Minor Changes - 51628fc: Initial commit ================================================ FILE: LICENSE ================================================ Copyright 2023 Matthew Pocock Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: package.json ================================================ { "name": "@total-typescript/ts-reset", "version": "0.6.1", "description": "A CSS reset for TypeScript, improving types for common JavaScript API's", "private": false, "repository": "https://github.com/total-typescript/ts-reset", "homepage": "https://totaltypescript.com/ts-reset", "scripts": { "dev": "tsc --watch", "build": "tsx scripts/build.ts", "ci": "turbo build check-exports lint lint-pkg-json format:check", "check-exports": "check-export-map", "lint": "tsc", "lint-pkg-json": "tsx scripts/lint.ts", "release": "turbo run publish", "publish": "changeset publish", "format:check": "prettier \"./src/**/**.ts\" --check", "format": "prettier --write \"./src/**/**.ts\" --write" }, "main": "./dist/recommended.js", "module": "./dist/recommended.mjs", "types": "./dist/recommended.d.ts", "exports": { "./package.json": "./package.json", ".": { "types": "./dist/recommended.d.ts", "import": "./dist/recommended.mjs", "default": "./dist/recommended.js" }, "./recommended": { "types": "./dist/recommended.d.ts", "import": "./dist/recommended.mjs", "default": "./dist/recommended.js" }, "./filter-boolean": { "types": "./dist/filter-boolean.d.ts", "import": "./dist/filter-boolean.mjs", "default": "./dist/filter-boolean.js" }, "./is-array": { "types": "./dist/is-array.d.ts", "import": "./dist/is-array.mjs", "default": "./dist/is-array.js" }, "./json-parse": { "types": "./dist/json-parse.d.ts", "import": "./dist/json-parse.mjs", "default": "./dist/json-parse.js" }, "./fetch": { "types": "./dist/fetch.d.ts", "import": "./dist/fetch.mjs", "default": "./dist/fetch.js" }, "./array-includes": { "types": "./dist/array-includes.d.ts", "import": "./dist/array-includes.mjs", "default": "./dist/array-includes.js" }, "./set-has": { "types": "./dist/set-has.d.ts", "import": "./dist/set-has.mjs", "default": "./dist/set-has.js" }, "./promise-catch": { "types": "./dist/promise-catch.d.ts", "import": "./dist/promise-catch.mjs", "default": "./dist/promise-catch.js" }, "./map-constructor": { "types": "./dist/map-constructor.d.ts", "import": "./dist/map-constructor.mjs", "default": "./dist/map-constructor.js" }, "./map-has": { "types": "./dist/map-has.d.ts", "import": "./dist/map-has.mjs", "default": "./dist/map-has.js" }, "./utils": { "types": "./dist/utils.d.ts", "import": "./dist/utils.mjs", "default": "./dist/utils.js" }, "./array-index-of": { "types": "./dist/array-index-of.d.ts", "import": "./dist/array-index-of.mjs", "default": "./dist/array-index-of.js" }, "./dom": { "types": "./dist/dom.d.ts", "import": "./dist/dom.mjs", "default": "./dist/dom.js" }, "./storage": { "types": "./dist/storage.d.ts", "import": "./dist/storage.mjs", "default": "./dist/storage.js" } }, "keywords": [], "author": "Matt Pocock", "license": "MIT", "devDependencies": { "@changesets/cli": "^2.27.3", "@types/node": "^18.19.33", "check-export-map": "^1.3.1", "prettier": "^3.2.5", "tsx": "^3.14.0", "turbo": "^1.13.3", "typescript": "^5.5.4" }, "prettier": { "arrowParens": "always", "trailingComma": "all", "semi": true, "printWidth": 80, "singleQuote": false, "tabWidth": 2, "useTabs": false }, "packageManager": "pnpm@9.7.1+sha512.faf344af2d6ca65c4c5c8c2224ea77a81a5e8859cbc4e06b1511ddce2f0151512431dd19e6aff31f2c6a8f5f2aced9bd2273e1fed7dd4de1868984059d2c4247" } ================================================ FILE: readme.md ================================================ ![TS Reset - Improved TypeScript's Built-in Typings](https://raw.githubusercontent.com/mattpocock/ts-reset/main/og-image.png) **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! ## Official Docs Check out our docs page on [Total TypeScript](https://totaltypescript.com/ts-reset) ================================================ FILE: scripts/build.ts ================================================ import * as fs from "fs/promises"; import * as path from "path"; const entrypointDir = path.join(__dirname, "../", "src", "entrypoints"); const distDir = path.join(__dirname, "../", "dist"); const run = async () => { try { await fs.mkdir(distDir); } catch (e) {} const entrypoints = await fs.readdir(entrypointDir); for (const entrypoint of entrypoints) { const entrypointBase = entrypoint.replace(".d.ts", ""); await Promise.all([ fs.writeFile(path.join(distDir, `${entrypointBase}.js`), ""), fs.writeFile(path.join(distDir, `${entrypointBase}.mjs`), ""), fs.copyFile( path.join(entrypointDir, entrypoint), path.join(distDir, `${entrypointBase}.d.ts`), ), ]); } }; run().catch((e) => { console.error(e); process.exit(1); }); ================================================ FILE: scripts/lint.ts ================================================ import * as fs from "fs"; import * as path from "path"; const packageJsonContents = fs.readFileSync( path.join(__dirname, "../", "package.json"), "utf8", ); const packageJson = JSON.parse(packageJsonContents) as { exports: Record; }; const pkgJsonExports = Object.keys(packageJson.exports).filter((entrypoint) => { return entrypoint !== "."; // ignore the root entrypoint }); const entrypointFiles = fs .readdirSync(path.join(__dirname, "../src/entrypoints")) .map((file) => { return file.replace(".d.ts", ""); }); for (const entrypointFile of entrypointFiles) { if (!pkgJsonExports.includes(`./${entrypointFile}`)) { console.error( `Missing export file in package.json for ./src/entrypoints/${entrypointFile}.ts`, ); process.exit(1); } } ================================================ FILE: src/entrypoints/array-includes.d.ts ================================================ /// interface ReadonlyArray { includes( searchElement: T | (TSReset.WidenLiteral & {}), fromIndex?: number, ): boolean; } interface Array { includes( searchElement: T | (TSReset.WidenLiteral & {}), fromIndex?: number, ): boolean; } ================================================ FILE: src/entrypoints/array-index-of.d.ts ================================================ /// interface ReadonlyArray { lastIndexOf( searchElement: T | (TSReset.WidenLiteral & {}), fromIndex?: number, ): number; indexOf( searchElement: T | (TSReset.WidenLiteral & {}), fromIndex?: number, ): number; } interface Array { lastIndexOf( searchElement: T | (TSReset.WidenLiteral & {}), fromIndex?: number, ): number; indexOf( searchElement: T | (TSReset.WidenLiteral & {}), fromIndex?: number, ): number; } ================================================ FILE: src/entrypoints/dom.d.ts ================================================ /// /// ================================================ FILE: src/entrypoints/fetch.d.ts ================================================ interface Body { json(): Promise; } ================================================ FILE: src/entrypoints/filter-boolean.d.ts ================================================ /// interface Array { filter( predicate: BooleanConstructor, thisArg?: any, ): TSReset.NonFalsy[]; } interface ReadonlyArray { filter( predicate: BooleanConstructor, thisArg?: any, ): TSReset.NonFalsy[]; } ================================================ FILE: src/entrypoints/is-array.d.ts ================================================ interface ArrayConstructor { isArray(arg: any): arg is unknown[]; } ================================================ FILE: src/entrypoints/json-parse.d.ts ================================================ interface JSON { /** * Converts a JavaScript Object Notation (JSON) string into an object. * @param text A valid JSON string. * @param reviver A function that transforms the results. This function is called for each member of the object. * If a member contains nested objects, the nested objects are transformed before the parent object is. */ parse( text: string, reviver?: (this: any, key: string, value: any) => any, ): unknown; } ================================================ FILE: src/entrypoints/map-constructor.d.ts ================================================ interface MapConstructor { new (): Map; } ================================================ FILE: src/entrypoints/map-has.d.ts ================================================ /// interface Map { has(value: K | (TSReset.WidenLiteral & {})): boolean; } interface ReadonlyMap { has(value: K | (TSReset.WidenLiteral & {})): boolean; } ================================================ FILE: src/entrypoints/promise-catch.d.ts ================================================ interface Promise { /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of which ever callback is executed. */ then( onfulfilled?: | ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: | ((reason: unknown) => TResult2 | PromiseLike) | undefined | null, ): Promise; /** * Attaches a callback for only the rejection of the Promise. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of the callback. */ catch( onrejected?: | ((reason: unknown) => TResult | PromiseLike) | undefined | null, ): Promise; } ================================================ FILE: src/entrypoints/recommended.d.ts ================================================ /// /// /// /// /// /// /// /// /// /// ================================================ FILE: src/entrypoints/set-has.d.ts ================================================ /// interface Set { has(value: T | (TSReset.WidenLiteral & {})): boolean; } interface ReadonlySet { has(value: T | (TSReset.WidenLiteral & {})): boolean; } ================================================ FILE: src/entrypoints/storage.d.ts ================================================ interface Storage { [name: string & {}]: unknown; } ================================================ FILE: src/entrypoints/utils.d.ts ================================================ declare namespace TSReset { type NonFalsy = T extends false | 0 | "" | null | undefined | 0n ? never : T; type WidenLiteral = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T extends bigint ? bigint : T extends symbol ? symbol : T; } ================================================ FILE: src/tests/array-includes.ts ================================================ import { doNotExecute, Equal, Expect } from "./utils"; doNotExecute(async () => { const arr = ["1", "2", "3"] as const; // Look ma, no error! arr.includes("4"); // Look ma, proper errors! arr.includes( // @ts-expect-error 2, ); arr.includes( // @ts-expect-error true, ); }); doNotExecute(async () => { const arr = [1, 2, 3] as const; arr.includes(4); arr.includes( // @ts-expect-error true, ); arr.includes( // @ts-expect-error "str", ); }); doNotExecute(async () => { const arr = [ { a: 1 }, { a: 2, }, { a: 3, }, ] as const; arr.includes( // @ts-expect-error 4, ); arr.includes({ a: 1 }); }); doNotExecute(async () => { const arr: Array<"1" | "2" | "3"> = ["1", "2", "3"]; arr.includes("4"); arr.includes( // @ts-expect-error 2, ); arr.includes( // @ts-expect-error true, ); }); ================================================ FILE: src/tests/array-index-of.ts ================================================ import { doNotExecute, Equal, Expect } from "./utils"; doNotExecute(async () => { const arr = ["1", "2", "3"] as const; // Look ma, no error! arr.indexOf("4"); // Look ma, proper errors! arr.indexOf( // @ts-expect-error 2, ); arr.indexOf( // @ts-expect-error true, ); }); doNotExecute(async () => { const arr = [1, 2, 3] as const; arr.indexOf(4); arr.indexOf( // @ts-expect-error true, ); arr.indexOf( // @ts-expect-error "str", ); }); doNotExecute(async () => { const arr = [ { a: 1 }, { a: 2, }, { a: 3, }, ] as const; arr.indexOf( // @ts-expect-error 4, ); arr.indexOf({ a: 1 }); }); doNotExecute(async () => { const arr: Array<"1" | "2" | "3"> = ["1", "2", "3"]; arr.indexOf("4"); arr.indexOf( // @ts-expect-error 2, ); arr.indexOf( // @ts-expect-error true, ); }); doNotExecute(() => { const arr: string[] | number[] = {} as any; const result = arr.indexOf("abc"); }); // lastIndexOf doNotExecute(async () => { const arr = ["1", "2", "3"] as const; // Look ma, no error! arr.lastIndexOf("4"); // Look ma, proper errors! arr.lastIndexOf( // @ts-expect-error 2, ); arr.lastIndexOf( // @ts-expect-error true, ); }); doNotExecute(async () => { const arr = [1, 2, 3] as const; arr.lastIndexOf(4); arr.lastIndexOf( // @ts-expect-error true, ); arr.lastIndexOf( // @ts-expect-error "str", ); }); doNotExecute(async () => { const arr = [ { a: 1 }, { a: 2, }, { a: 3, }, ] as const; arr.lastIndexOf( // @ts-expect-error 4, ); arr.lastIndexOf({ a: 1 }); }); doNotExecute(async () => { const arr: Array<"1" | "2" | "3"> = ["1", "2", "3"]; arr.lastIndexOf("4"); arr.lastIndexOf( // @ts-expect-error 2, ); arr.lastIndexOf( // @ts-expect-error true, ); }); doNotExecute(() => { const arr: string[] | number[] = {} as any; const result = arr.lastIndexOf("abc"); }); ================================================ FILE: src/tests/fetch.ts ================================================ import { doNotExecute, Equal, Expect } from "./utils"; doNotExecute(async () => { const result = await fetch("/").then((res) => res.json()); type tests = [Expect>]; }); doNotExecute(async () => { // Make tests fail when someone tries to PR res.json const result = await fetch("/").then((res) => { // @ts-expect-error return res.json(); }); }); ================================================ FILE: src/tests/filter-boolean.ts ================================================ import { doNotExecute, Equal, Expect } from "./utils"; doNotExecute(() => { const arr = [1, 2, 3, undefined]; const result = arr.filter(Boolean); type tests = [Expect>]; }); doNotExecute(() => { const arr = ["1", "2", undefined] as const; const result = arr.filter(Boolean); type tests = [Expect>]; }); doNotExecute(() => { const arr = [0, null, undefined, false, ""] as const; const result = arr.filter(Boolean); type tests = [Expect>]; }); doNotExecute(() => { const arr: (0 | null | undefined | false | "" | 0n)[] = [ 0, null, undefined, false, "", 0n, ]; const result = arr.filter(Boolean); type tests = [Expect>]; }); doNotExecute(() => { const arr: string[] | number[] = {} as any; const result = arr.filter((x) => typeof x === "string"); type tests = [Expect>]; }); ================================================ FILE: src/tests/is-array.ts ================================================ import { doNotExecute, Equal, Expect } from "./utils"; doNotExecute(() => { const maybeArr = [1, 2, 3] as unknown; if (Array.isArray(maybeArr)) { type tests = [Expect>]; } }); doNotExecute(() => { const arrOrString = [] as string[] | string; if (Array.isArray(arrOrString)) { type tests = [Expect>]; } }); doNotExecute(() => { let path: string | string[] = []; const paths = Array.isArray(path) ? path : [path]; type tests = [Expect>]; }); ================================================ FILE: src/tests/json-parse.ts ================================================ import { doNotExecute, Equal, Expect } from "./utils"; doNotExecute(() => { const result = JSON.parse("{}"); type tests = [Expect>]; }); doNotExecute(() => { // Make tests fail when someone tries to PR JSON.parse // @ts-expect-error const result = JSON.parse("{}"); }); ================================================ FILE: src/tests/map-constructor.ts ================================================ import { doNotExecute, Equal, Expect } from "./utils"; doNotExecute(() => { const map = new Map(); const result = map.get("foo"); type test = [Expect>]; }); doNotExecute(() => { const map = new Map(); map.set("Jessie", { phone: "213-555-1234", address: "123 N 1st Ave" }); const result = map.has("Jessie"); type test = [Expect>]; }); doNotExecute(() => { const map = new Map(); map.set("Jessie", { phone: "213-555-1234", address: "123 N 1st Ave" }); const result = map.get("Jessie"); type test = [Expect>]; }); doNotExecute(() => { const map = new Map(); map.set("Jessie", { phone: "213-555-1234", address: "123 N 1st Ave" }); const result = map.delete("Jessie"); type test = [Expect>]; }); doNotExecute(() => { const map = new Map(); map.set("Jessie", { phone: "213-555-1234", address: "123 N 1st Ave" }); map.set("Hilary", { phone: "617-555-4321", address: "321 S 2nd St" }); const size = map.size; type testSize = [Expect>]; }); doNotExecute(() => { const map = new Map(); map.set("Jessie", { phone: "213-555-1234", address: "123 N 1st Ave" }); map.set("Hilary", { phone: "617-555-4321", address: "321 S 2nd St" }); const cleared = map.clear(); type testClear = [Expect>]; }); doNotExecute(() => { const map = new Map() satisfies Map; type test = [Expect>>]; }); doNotExecute(() => { const map: Map = new Map(); type test = [Expect>>]; }); doNotExecute(() => { function expectsBooleanMap(map: Map) { return map; } const map = expectsBooleanMap(new Map()); type test = [Expect>>]; }); doNotExecute(() => { const map = new Map([ ["foo", 1], ["bar", 2], ]); type test = [Expect>>]; }); ================================================ FILE: src/tests/map-has.ts ================================================ import { doNotExecute } from "./utils"; doNotExecute(() => { const map = new Map([ [1, "1"], [2, "2"], [3, "3"], ] as const); map.has(4); map.has( // @ts-expect-error "4", ); map.has( // @ts-expect-error true, ); }); doNotExecute(() => { const map = new Map([ [1, "1"], [2, "2"], [3, "3"], ] as const) as ReadonlyMap<1 | 2 | 3, "1" | "2" | "3">; map.has(4); map.has( // @ts-expect-error "4", ); map.has( // @ts-expect-error true, ); }); ================================================ FILE: src/tests/promise-catch.ts ================================================ import { doNotExecute, Equal, Expect } from "./utils"; doNotExecute(async () => { Promise.reject("string instead of Error").catch((err) => { type test = Expect>; }); Promise.reject("string instead of Error").then( () => {}, (err) => { type test = Expect>; }, ); }); ================================================ FILE: src/tests/set-has.ts ================================================ import { doNotExecute, Equal, Expect } from "./utils"; doNotExecute(() => { const set = new Set([1, 2, 3] as const); set.has(4); set.has( // @ts-expect-error "4", ); set.has( // @ts-expect-error true, ); }); doNotExecute(() => { const set = new Set([1, 2, 3] as const) as ReadonlySet<1 | 2 | 3>; set.has(4); set.has( // @ts-expect-error "4", ); set.has( // @ts-expect-error true, ); }); ================================================ FILE: src/tests/storage.ts ================================================ import { doNotExecute, Equal, Expect } from "./utils"; // Ensure that arbitrary access to localStorage is not allowed doNotExecute(() => { type test = Expect>; // @ts-expect-error localStorage.a.b.c; }); // Ensure that the type of localStorage remains correct doNotExecute(() => { type tests = [ Expect string | null>>, Expect< Equal void> >, Expect void>>, Expect void>>, Expect string | null>>, Expect>, ]; }); // Ensure that arbitrary access to sessionStorage is not allowed doNotExecute(() => { type test = Expect>; // @ts-expect-error sessionStorage.a.b.c; }); // Ensure that the type of sessionStorage remains correct doNotExecute(() => { type tests = [ Expect< Equal string | null> >, Expect< Equal void> >, Expect void>>, Expect void>>, Expect string | null>>, Expect>, ]; }); ================================================ FILE: src/tests/utils.ts ================================================ export type Expect = T; export type ExpectTrue = T; export type ExpectFalse = T; export type IsTrue = T; export type IsFalse = T; export type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false; export type NotEqual = true extends Equal ? false : true; // https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360 export type IsAny = 0 extends 1 & T ? true : false; export type NotAny = true extends IsAny ? false : true; export type Debug = { [K in keyof T]: T[K] }; export type MergeInsertions = T extends object ? { [K in keyof T]: MergeInsertions } : T; export type Alike = Equal, MergeInsertions>; export type ExpectExtends = EXPECTED extends VALUE ? true : false; export type ExpectValidArgs< FUNC extends (...args: any[]) => any, ARGS extends any[], > = ARGS extends Parameters ? true : false; export type UnionToIntersection = ( U extends any ? (k: U) => void : never ) extends (k: infer I) => void ? I : never; export const doNotExecute = (func: () => any) => {}; ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "module": "commonjs", /* Specify what module code is generated. */ "noEmit": true, /* Disable emitting files from a compilation. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ "strict": true, /* Enable all strict type-checking options. */ }, "exclude": [ "dist", "node_modules" ] } ================================================ FILE: turbo.json ================================================ { "$schema": "https://turbo.build/schema.json", "pipeline": { "build": { "outputs": ["dist"] }, "check-exports": { "dependsOn": ["build"] }, "lint": {}, "format-check": {}, "lint-pkg-json": {}, "publish": { "dependsOn": ["build", "check-exports", "lint", "lint-pkg-json"], "cache": false } } }