8

I am wondering if it is possible given a file with an exported type, could the types be extracted from the generic, to a computed type?

function example() {
  return {
    foo: 'hi',
    bar: true,
    baz: 1
  };
}

export type Signature = ReturnType<typeof example>;

To this:

export type Signature = {
    foo: string;
    bar: boolean;
    baz: number;
}

I don't believe that the tsc cli does this, and I'm not sure what this process would be called.

Can tsc create this export?

Are there any third-party tools that can do this?

The only way I know how to do this is hover over the variable in VSCode and copy the computed type, and that only works if it's short, and doesn't trail off ...

I found some other references to similar requests:

ThomasReggi
  • 42,912
  • 63
  • 199
  • 343

1 Answers1

6

Looking into this question, without replicating VSCode's hover.ts, I find that one, if not the only, answer to this question is:

Extract that hover "quickinfo" from tsserver

You need to communicate the line and char offset in your file to the server. One should be able to use abstract syntax tree (AST) in .ts for that. I wrote a client in bash instead. Attached below, it supports calculating char offset from column (as seen in the VSCode statusbar). A log from a run of your source and a document position on the word "Signature" follows and the end comes very close to your wanted extraction.

$ (prepared=('open -s #.ts -i file' 'quickinfo -s #.ts -i file -n 9 -i line -n 20 -i _column_')&&. $(which tsssh))
ts < open -s #.ts -i file
{"seq": 1, "type": "request", "command": "open", "arguments": {"file": "#.ts"}}
ts < quickinfo -s #.ts -i file -n 9 -i line -n 20 -i _column_
{"seq": 1, "type": "request", "command": "quickinfo", "arguments": {"file": "#.ts", "line": 9, "offset": 19}}
event #0
event: typingsInstallerPid
body: {"pid": 65341}
response #0 (1)
command: quickinfo
body: {"kind": "type", "kindModifiers": "export", "start": {"line": 9, "offset": 13}, "end": {"line": 9, "offset": 22}, "displayString": "type Signature = {\n    foo: string;\n    bar: boolean;\n    baz: number;\n}", "documentation": "", "tags": []}
displayString: type Signature = {
    foo: string;
    bar: boolean;
    baz: number;
}
  • Quick and dirty client for https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29..
  • Relies heavily on prerequisite http://kmkeen.com/jshon/, and compatible bash and gawk, while tput and tee could be cut out I guess..
  • Developed on macOS 10.13.6 in version 5.0.16 bash -O cmdhist -O lithist with macports, and various other shopt..
  • NB: You need to poll event/response manually by sending empty commandlines. Exit with kill -sigint , typically ctrl+C..
  • Run in local repo like tsserver=./node_modules/typescript/bin/tsserver tsssh..
  • Disable history with histfile= path/to/tsssh. Multiple "prepared" can be specified as (prepared=("first" ... "last")&&. path/to/tsssh)
#!/usr/bin/env bash
( # Quick and dirty client for https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29..
  # Relies heavily on prerequisite http://kmkeen.com/jshon/, and compatible bash and gawk, while tput and tee could be cut out I guess..
  # Developed on macOS 10.13.6 in version 5.0.16 `bash -O cmdhist -O lithist` with macports, and various other shopt..
  # NB: You need to poll event/response manually by sending empty commandlines. Exit with kill -sigint , typically ctrl+C..
  # Run in local repo like `tsserver=./node_modules/typescript/bin/tsserver tsssh`..
  # Disable history with `histfile='' path/to/tsssh`. Multiple "prepared" can be specified as `(prepared=("first" ... "last")&&. path/to/tsssh)`
  # Copyright 2020 Viktor Bergquist (vike2000@gmail.com), license https://creativecommons.org/licenses/by-sa/4.0/
  HISTFILE=${histfile-.tsssh_history} &&
  { ((\!-( x = xtrace )))||set -x ;} && ((!errexit))||set -e &&
  state()(set +x;tput setaf $1;echo "${*:2}";tput sgr0;((\!-x))||set -x) >&2 && trap 'state 1 $?: "$BASH_COMMAND"' err &&
  [[ ! $HISTFILE ]]||{ h=$HISTSIZE&&HISTSIZE=1&&set -o history&&HISTSIZE=$h&&history -s ''&&{ history -r "$HISTFILE"||:;};}&&
  coproc ts (${tsserver:-tsserver}) &&set -o pipefail&&command sleep 1 &&
  show(){ set +x; f=$1&&shift && i=$(cat) && declare -n n&&for n;do
      n=$(((\!-x))||set -x; jshon <<<"$i" -e "${!n}" $f);((e=$?)) || echo "${!n}: $n"; done; ((\!-x))||set -x; return $e;} &&
  extract(){ show "$@";} >/dev/null &&
  compact()( "$@"|gawk -vORS= '{sub("^\\s+",!m[0]?"":" ")}1;{match($0,",$",m)}END{printf"\n"}') &&
  function check(){ while {
        read -rt.1 -d$'\r' h&&[[ $h =~ Content-Length:\ ([0-9]+) ]]&&read -r$t -N3&&read -r$t -N$((${BASH_REMATCH[1]}-1)) a;} <&${ts[0]};do
      #set +x;echo 'ts > '"$a";((\!-x))||set -x
                <<<"$a" extract -u type seq &&case $type in
      (   event)state 2 $type \#$seq
                <<<"$a" show -u event ;;&
      (response)<<<"$a" extract -u success request_seq&&case $success in
        ( false)state 1 $type \#$seq "($request_seq)"
                <<<"$a" show -u command message ;;
        (  true)state 2 $type \#$seq "($request_seq)"
                <<<"$a" show -u command message metadata 2>/dev/null ;;
        (     *)state 1 "unknown success: $success" ;esac ;&
      (   event)<<<"$a" compact show '' body ;! [[ $type = response && $success = true ]]||case $command in
        (     *)<<<"$a" extract '' body ;;&
        ( quickinfo) <<< "$body" show -u displayString ;esac ;;
      (       *)state 1 "unknown type: $type" ;esac ;done;} &&
  seq=0 && for p in ${!prepared[@]} -1;do for fd in <(echo "${prepared[p]}") 0;do fd="${fd##*/}"; (((p<0)!=(0<fd)))||continue
      while check <&-; set +x; read -p 'ts < ' -era Q; do
        ((\!${#Q[*]}))&&{ tput -S<<<$'cuu1\nel'
          ((\!-x))||set -x;} ||
        { ((\!-x))||set -x
          history -s "${Q[*]}"&&{ [[ ! $HISTFILE ]]||{ [[ ! -a $HISTFILE ]]&&history -w "$HISTFILE"||history -a "$HISTFILE";};} &&
          d=$(compact jshon <<<{} -n$((++seq)) -i seq -s request -i type -s "$Q" -i command -n {} "${Q[@]:1}" -i arguments)
          A=$(compact jshon <<<{} "${Q[@]:1}")
          ! <<<"$A" extract -u file line _column_ 2>/dev/null ||
          { o=$(gawk -vT="${tabs:-4}" -vC="$_column_" FNR=="$line"'{
              t=-1;for(o=0;o<=length();o++){t++;if("\t"==substr($0,o,1)){t+=T-1;t-=t%T}if(t>=C)break}print o-1
              }' "$file") &&
            d=$(<<<"$d" compact jshon -e arguments -d _column_ -n "$o" -i offset -p);}
          tee   <<<"$d" /dev/stderr >&${ts[1]};}
        done<&$fd;done;done;set +x;echo bye) #ts < eg: reload -s index.ts -i file ^M quickinfo -s index.ts -i file -n 4 -i line -n 7 -i _column_

With all your question's good references, you surely know some "trail off" that you mention can be prevented with "compilerOptions":{"noErrorTruncation":true} in the tsconfig.json :)

To the end of those references I can add that there's also an Interactive Diagnostics PR 31384 (draft) to hope for :)

For the time being I'll look into the interesting utility type in the related Q&A How can I see the full expanded contract of a Typescript type?

vike
  • 153
  • 2
  • 5