17

Take the following Typescript arrow function:

/**
 * Returns a probably unique component name.
 * 
 * @param baseName a suggested name to make unique.
 * @returns a probably unique name.
 */
export const getUniqueComponentName = (
  baseName
): string => {
  return baseName + Math.round(Math.random() * 10000000)
}

When Typescript is configured in tsconfig.json as such:

"noImplicitAny": true,

This correctly results in a compilation error:

[ts] Parameter 'baseName' implicitly has an 'any' type.

Visual Studio Code is also smart enough to inform you about this issue during development.

My goal is to create a precommit git hook that prevents such errors from ending up in version control. I tried to do this with tslint, husky and lint-staged using this npm script:

"lint": "tslint --project tsconfig.json --config tslint.json"

However, this does not result in the compilation error showing up by tslint. It is silently ignored.

I then tried to add a rule in tslint.json:

"typedef": [
      true,
      "arrow-parameter"
    ]

While this did make tslint complain, it also started to complain in anonymous arrow functions where the tsc compiler does not complain. In these arrow functions it should not be necessary to add types because the types were already set previously in the parent scope (they are inferred).

So basically, I would like for tslint to behave the same as tsc in this case. Anytime there is an error that would cause compilation to fail (such as the above arrow function), I would like to prevent the commit, but without actually compiling to Javascript. Is this possible?

Tom
  • 8,536
  • 31
  • 133
  • 232
  • 1
    Would running `tsc --noEmit` work? That is essentially running the static analyzer without generating JS. – y2bd Jul 19 '18 at 17:51
  • @y2bd yes I confirmed that works; is that a recommended way of doing it? in addition to running tslint? – Tom Jul 19 '18 at 18:04
  • In my workflow I usually do a normal build and just discard the artifacts (as well as a separate lint stage). If you're in a situation where that's not preferable this seems completely fine. – y2bd Jul 19 '18 at 18:29
  • @y2bd according to https://github.com/okonet/lint-staged/issues/412 it is not possible to have `lint-staged` run `tsc` only on the commited files. This is only possible if you don't pass in a `p` flag but then all config options are ignored. Also you cannot pass in the `paths` config as a command line argument, so not using `p` does not seem to be an option? – Tom Jul 19 '18 at 18:50
  • @y2bd I put up a 150 rep bounty if you could elaborate on my previous question – Tom Jul 22 '18 at 15:40

2 Answers2

14

I think your best bet is to run tsc --noEmit -p . and filter the output for errors in the modified files. For example, I saved the following script to tsc-some-files:

#!/bin/bash
declare -A include_files
for f in "$@"; do
  include_files["${f#$PWD/}"]=1
done
node_modules/.bin/tsc --noEmit -p . | (
  status=0
  show_continuation=false
  while IFS='' read -r line; do
    case "$line" in
    (' '*)
      if $show_continuation; then
        echo "$line" >&2
      fi
      ;;
    (*)
      file="${line%%(*}"
      if [ -n "${include_files["$file"]}" ]; then
        show_continuation=true
        echo "$line" >&2
        status=1
      else
        show_continuation=false
      fi
      ;;
    esac
  done
  exit $status
)

and set ./tsc-some-files as my lint-staged command, and it seemed to work. (Writing this in a programming language other than bash, if desired, is left as an exercise for the reader.)

Keep in mind though that editing one file can introduce an error in another file (e.g., if you changed the type of something that the other file is using), so I'd urge you to get your project clean of TypeScript errors ASAP by whatever hacks necessary (as long as you mark them so you can search for them later) and then set your hook to require no errors in the whole project. In fact, with respect to noImplicitAny in particular, when I migrated a JavaScript project to TypeScript several years ago, I wrote a script that inserted an explicit any everywhere there was an implicit any error, then I fixed the explicit anys at my leisure. I can share the script if you're interested.

Matt McCutchen
  • 28,856
  • 2
  • 68
  • 75
  • 1
    This will run tsc on the entire project, not just on the committed files – Tom Jul 26 '18 at 19:14
  • 1
    What is your concern? Performance, or that there are existing errors in other files that you want to ignore until those files are next modified? – Matt McCutchen Jul 26 '18 at 19:17
  • The second case mostly. The first one slightly :) – Tom Jul 26 '18 at 19:20
  • Updated the answer. – Matt McCutchen Jul 26 '18 at 19:48
  • Thanks for the script. I've tried integrating it with TS v3 and I'm getting weird errors like this one: `./scripts/ts-staged-files.sh: line 4: src/ui/Components/Select/Select.tsx: division by 0 (error token is "/Components/Select/Select.tsx")` – okonetchnikov Nov 07 '18 at 08:58
  • @okonetchnikov It looks like bash is trying to treat the filename as a string, which shouldn't happen if you copied the script correctly including the `declare -A` line. Can you please publish a repository that reproduces the problem? – Matt McCutchen Nov 08 '18 at 16:44
  • 2
    `-A` doesn’t work for me on Mac so I changed to `-a` assuming it’s the same. – okonetchnikov Nov 09 '18 at 20:36
  • @okonetchnikov That's your problem. You'll need to either get a version of bash that supports `declare -A` or find another solution, such as rewriting the script in a programming language that is supported in your environment. (I'm not interested in doing that work myself right now.) – Matt McCutchen Nov 10 '18 at 01:40
3

I don't have enough reputation to add this as a comment, but anyone that is getting an error similar to

./scripts/ts-staged-files.sh: line 4: 
   src/ui/Components/Select/Select.tsx: division by 0 
  (error token is "/Components/Select/Select.tsx")

I made this small modification to Matt McCutchen's answer to fix it.

#!/bin/bash

include_files=()

for f in "$@"; do
  include_files+=("${f#$PWD/}")
done
jmmendez
  • 91
  • 1
  • 3