Home > Blockchain >  why does the `as` keyword used to remap types break docs
why does the `as` keyword used to remap types break docs

Time:08-25

i'm extracting a type from an object and i'd like for the type keys to have the same docs as the object, here's a much shorter version of what i'm trying to achieve:

/** this is the object to extract type from */
const obj = {
  // the following should show when i use `as` inside of type creation
  /**  this is a id */
  id: undefined as string,
  /**  this is a num */
  num: 22 as number
};

and here's the types:

// extract keys with string type only
type UseAsKeyword<T> = {
  [K in keyof T as Extract<T[K], string> extends never ? never : K]: T[K];
};
type NoAsKeyword<T> = { [K in keyof T]: T[K] };
type ObjWithAs = UseAsKeyword<typeof obj>;
type ObjNoAs = NoAsKeyword<typeof obj>;

usage:

// this one doesn't show docs mentioned in `obj`
const testWithAs: ObjWithAs = { id: "hello" };
// this one shows docs mentioned in `obj`
const testNoAs: ObjNoAs = { id: "hello", num: 22 };

my tsconfig.json:

{
  // for all node depandant environments like Vite
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true,
    "strictNullChecks": false
  },
}

screenshots from typescript playground:
no docs on hover
docs on hover

I'd like to use the as keyword and maintain property/key docs, if this is not possible, then what's the alternative to remapping keys but maintain docs?

TypeScript Playground example

CodePudding user response:

apparently using the as keyword populates a new type detached from the passed one, so the only way to keep the type persistent is to use the Omit<> utility type as suggested by @WiktorZychla.
so the following type would be changed to:

// remapping keys with `as` directly, breaks docs reference
type UseAsKeyword<T> = {
  [K in keyof T as Extract<T[K], string> extends never ? never : K]: T[K];
};
// omiting keys remapped with `as`, maintains docs reference
type UseAsKeywordOmit<T> = Omit<
  {[K in keyof T]: T[K]}, 
  keyof {
  [K in keyof T as Extract<T[K], string> extends never ? K : never]: T[K];
}>;

usage Typescript Playground Example:

// hover over id, you'll see no docs reference
const testWithAs: UseAsKeyword<typeof obj> = { id: "hello" }; 

// hover over id, you'll see docs reference
const testWithAsOmit: UseAsKeywordOmit<typeof obj> = { id: "hello" };

now this introduces a couple of hassles like:

  • more Omit keyword,curly and angled brackets will have to be written and it'll get uglier the longer the type is.(check out DeepVoid at this gist)
  • the logic has to be inversed to exclude what's not needed instead of including what's needed, which sometimes gets tricky and is not as simple. but in the end, it gets the job done.

CodePudding user response:

The rather verbose definition I suggested in a comment

type UseAsKeywordOmit<T> = Omit<
  {[K in keyof T]: T[K]}, 
  keyof {
  [K in keyof T as Extract<T[K], string> extends never ? K : never]: T[K];
}>;

can be simplified to

type UseAsKeywordOmit<T> = Omit<T, keyof { [K in keyof T as T[K] extends string ? never : K]: T[K]; }>;

This is because the

{[K in keyof T]: T[K]}

is obviously just

T

and the Exclude can be inlined here so it's not needed.

I hope this shorter version suits you even better.

  • Related