tsc-silent. A TypeScript compiler with --suppress flag

tsc-silent output

tsc-silent is a tiny tool, which helps a lot. Similarly to -nowarn in C#, it supports ‑‑suppress or ‑‑suppressConfig flags and replaces tsc when you wish to ignore certain errors in the compiler’s output. The most useful feature is the ability to ignore specific errors coming from a certain file path or file pattern.

Okay, Compiler…

With TypeScript, I often spend time fixing problems that are revealed after turning another compiler’s flag on.

When I do so, my routine is:

  • turn the compiler option on (say, --strictNullChecks in many-years-old-legacy-code)
  • fix TS problems
  • profit commit

However, it’s quite often that the list of errors is longer than my “can fix in a day” capability. And sometimes it takes weeks to make all the necessary changes before I can finally turn on the flag in the main branch. So, this “fix TS problems” step is repeated day after day.

Of course, while you work on these errors, there is a good chance that other developers’ code, committed to the project, brings new errors because the flag is still off in the main branch. And you have to fix these too.

With tsc-silent in place, I first turn the compiler option on (ignoring errors coming from some directories) in the main branch and my daily routine changes to:

  • fix a unit of “can fix in a day” problems, and exclude fixed files from the list of ignored paths
  • commit and profit every day

Ignore responsibly

Even though tsc-silent helps a lot, it can be risky if you aren’t careful enough.

Error codes that are too vague

Say, for good reasons, you try to turn on --strictFunctionTypes. It will report some new errors. For example, as in the case from TypeScript@2.6 announce:

Code sample with --strictFunctionTypes

It fails with a very generic error #2322. There are many other problems reported under this code, and they will all be silently ignored. If it is the case that you turn off a similar umbrella error somewhere, you may end up in more trouble than you were before you decided to introduce --strictFunctionTypes.

Error codes change

As @RyanCavanaugh mentioned, error codes change. With every release TypeScript brings new or more specific errors, so you have to manually control TypeScript upgrades to avoid problems.

Is the flag to become standard?

There is a suggestion to ignore errors in the compiler’s output by id, where valid concerns were raised by the TypeScript team, and I concur with them.

Except for the practical reasons mentioned above, I don’t agree, that the compiler should allow private members’ access in test files. From a pragmatic point of view, I cannot trust something that forgives my errors based on whether or not the file name matches a regexp hidden in the project config. They are still errors, and I want to see these errors in IDE myself; I want to be sure that everyone sees them because I wish for these errors to get fixed.

I doubt that the flag will ever become a part of the compiler. Luckily, TypeScript is a great extendable tool, and when extended and used with caution it can even handle such non-trivial use cases.

Type aliases to `any`

any solves any problem

Cicerone

As you know, any considered harmful in TypeScript. I do believe it’s better to avoid it, and especially after unknown was introduced to replace any in lots of cases. To discourage developers from using it, I can even imagine no-any rule turned on as a warning, but I don’t think deprecating any should be a goal.

Having a number of dependencies, it might be problematic to integrate all the third-party code without using any. Sometimes, when you upgrade a single package, some of its’ types rely on other package’s types, so you have to add a sprinkle of any. Or, pretty commonly, every TypeScript upgrade brings a handful of new errors across whole codebase. And if you don’t have a full picture or even authority to make substantial changes in someone else’s code, at times you have to suppress these errors with another splash of any. Thus, maintaining zero-any codebase seems to me excessively and unduly time consuming.

But we still want to discourage people from using it, and that’s where type aliases to any became handy:

/** */
type temporaryAny = any;

temporaryAny solves refactoring problems mentioned above by not using any and simplifying spotting refactoring-only any.

Once you see the benefits, you realise there is nothing that stops you from annotating every non-semantic any with temporaryAnyUntillCertainProblemSolved:

/**
 * Type alias for string types that have to be narrowed to literal types
 */
type temporaryString = string;

/**
 * Type alias for number types that have to be narrowed to literal types
 * https://github.com/Microsoft/TypeScript/issues/15480
 */
type temporaryNumber = number;

/**
 * Used for situations when conditional types (for example `ReturnValue<>`) should be used.
 */
type temporaryAnyUntilTypescript2_8 = temporaryAny;

And even seemingly too verbose:

/**
 * For generic type `T`:
 * ```
 * const bar: T["bar"] // string | null | undefined
 * if (bar) {
 *  bar // is still `string | null | undefined`
 * }
 * ```
 * https://github.com/Microsoft/TypeScript/issues/22214
 */
type temporaryAnyWhileTypeGuardsNotWorkingForIndexedTypesWithGenerics = temporaryAny;

Every time I type any I wonder if it’s an any or temporary any.

Note on TypeScript's exhaustive type checks in scope of Redux's reducer

After introducing discriminated union types, they showed a way to get closer to pattern matching by havin exhaustiveness checks in default branch of a switch-case statement.

In documentation there is an implementation of an assertion function, that returns never both runtime- and type-wise.

function assertNever(x: never): never {
    throw new Error("Unexpected object: " + x);
}

Intuitively, similar approach could be used in Redux’ reducer function to make sure each action has been handled:

type Action = TurnOnAction | TurnOffAction;
function reducer(state: State, action: Action) {
    switch (action.type) {
        case TURN_ON:
            return ...
        case TURN_OFF:
            return ...
        default:
            assertNever(action);
            ...

The check seems fair type-wise (action indeed should have type never). Apparently, we don’t exhaustively check all the possible actions, instead we specify a subset of actions we handle in the particular reducer (type Action = ...). We don’t care about the other actions (such as internal @@redux/INIT), they just fall through to default case. But that way we have a runtime problem, because assertNever throws on every call, so what do we do?

Right, we add a second parameter specifying whether we throw or not:

function assertNever(value: never, noThrow?: boolean): never {
  if (noThrow) {
    return value
  }
  throw new Error(...);
}

Fair enough, but, we are about to make a mistake. See, with noThrow === true signature still says the function returns never, while it does return a value.

What important about exhaustive checks, is that in most cases assertNever should be prepended with return statement so that compiler inters correct return type of a function. This habit and the fact that never is being omitted from union types, may lead to

function reducer(...): State
    switch (action.type) {
        case TURN_ON:
            return ...  // State
        case TURN_OFF:
            return ...  // State
        default:
            return assertNever(action, true); // never
    }
}

And voilà, return type of a function is State | State | never simplified to State and we have happy compiler and a runtime error.

Advise if simple: don’t pretend you return never in the case you return anything. There is unknown type, which absorbs every type in union, and turned to be a pretty good replacement for never in this case. That’s how assertNever could be implemented:

function assertNever(value: never): never;
function assertNever(value: never, noThrow: true): unknown;
function assertNever(value: never, noThrow?: true): never | unknown {
    if (!noThrow) {
        throw new Error("...");
    }
    return undefined as never;
}    

Or, alternatively, you can separate implementations:

/** Non throwing one */
function assertNever(value: never): unknown {...}
/** The one that throws */
function assertNeverEver(value: never): never {...}

Updated, thanks to @fljot.

It was a mistake to even think about a non-throwing assertNever. If the non-throwing one is being used only in reducer’s default branch, then so be it. Let’s just endReducer:

function assertNever(x: never): never {
    throw new Error("Unexpected object: " + x);
}

function endReducer<T>(state: T, action: never): T {
    return state;
}

The simpler the better:

function reducer(state: State, action: Actions)...
    switch (action.type) {
        case ...

        default:
            return endReducer(state, action);
    }
}

Updated, thanks to u/AngularBeginner .

Other option is to have throwing and non-throwing functions with the same signature:

function assertNever(x: never): never { throw new Error(`Unxpected value: ${x}`); }
function ensureNever(x: never): void { /* Intentionally empty */ }

Stay safe ♱

Prettier makes your code shittier

As the whole idea of Prettier can fit in a tweet, my response could probably fit in a simple «I don’t like the way you solve a possibly nonexistent problem» too.

But since they become so popular, I decided to take apart a paragraph from Prettier’s homepage

By far the biggest reason for adopting Prettier is to stop all the on-going debates over styles. It is generally accepted that having a common style guide is valuable for a project and team but getting there is a very painful and unrewarding process

Well…

By far the biggest reason for adopting Prettier is to stop all the on-going debates over styles

That does sound reasonable, but we never fight for the right place for a new line. We do fight for optimal line-length and function vs =>, but Prettier doesn’t help you to solve any of these problems

having a common style guide is valuable for a project and team

Amen!

but getting there is a very painful and unrewarding process

It is, indeed! But let’s take a look at object shorthands. They look better and even easier to read. However, I do not follow this rule because TypeScript refactoring tool doesn’t work in that case. And I value the ability to rename object’s members way more than concise syntax. Same goes to default import/export, which we avoid. Thus, if you care about maintainability, you still have to have tslint, most likely with custom rules developed within your company.

It is painful to get there, but it’s not very painful for sure. And that is a really rewarding process, which pays you back immediately.

To me, Prettier does help you in places where you don’t care about code style and does not allow you to have a custom format in places where it matters, sometimes it reduces readability:

export const selectIsImmersiveLayout: StoreSelector<boolean> = () => false;

// prettier ↓

export const selectIsImmersiveLayout: StoreSelector<
  boolean
> = () => false;

sometimes, it propagates inconsistency and causes git conflicts by changing more than you intended to:

function foo(does, fit)
// single rename results in several lines changed ↓
function foo(
  doesnt,
  fit,
)

prevents you from manually formatting your code where it matters, say, complicated logic conditions:

if (
    A
    && B
    && (
        C
        ||
        D
    )
) {
// prettier ↓
if (A &&
    B &&
    (C || D)
//  or whatever mathes configured line length,
//  but not the format you put there

so why?

How to be an outstanding Javascript developer

There are so many exciting things happening in Javascript world. It’s so great to float in the ocean full of new frameworks, isomorphic apps and higher order components. It’s pure joy. However, being outstanding developer isn’t easy.

Here is the thing, you are either shark or sheep in the community. Nowadays, you have to be a shark, by all means, there is no time to look around or back.

I hope that comes without saying. Follow “Javascript Weekly”. Star new projects on GitHub. Retweet as many announces as you can.

It’s okay if you don’t have time to dig into a new framework, no one has. At least you help the community to stay aware and improve by retweeting and starring.

Know your side

It’s a war.

There are sharks, on one side of the barricade, who try to keep up and be on the crest of a wave. These guys are always ready to introduce new awesome framework and make the world a better place.

There are sheep on the other side. These naggers always concerned, busy refactoring something and hardly know how awesome react-create-app is.

Do you wanna be a retrograde or innovative? There it is.

Be functional

Functional programming is a must today. The good thing is it won’t take much of your time. You have to know what is a pure function (it’s good), side-effects (they are bad) and remember that there are no side-effects in functional code. Ah, also, there is a mystical “monad” who no one understands. Keep that in mind and don’t push too hard trying to wrap your head around it.

Love Javascript

In order to be a good Javascript developer, you have to love Javascript.

Here it is, our philosophy is basically this, and this is something that we live by, and we always have, and we always will. Don't ever, for any reason, ever, no matter what, no matter where, or who, or who you are with, or where you are going, or where you've been, ever, for any reason whatsoever, think that there is something better than Javascript.

Be opinionated

Your opinion is another thing that makes you delightful companion. See, you love javascript, follow the trends, you are smart and registered on Twitter. So there is no way you don’t have an opinion on a particular thing.

— How do you compare Angular and React? Don’t they solve different problems?

— Duh, they are both frameworks. And React has three times more stars on GitHub, and it beats stupid Angular in ToDoMVC! React is way better than Angular!

— What do you think of types in Javascript?

— Javascript is great, and you don’t need types!

— Well, it still really want to try them, just not sure how TypeScript is different from Flow…

— Of course, Flow is better, because they know the shit at Facebook!

– Hey, what do you think about NPM and Yarn?

— Of course, Yarn is better, because they know the shit at Facebook!

You can even allow yourself to be a bit of an asshole. Smart people are usually like that.

– Dude, I feel like I struggle a lot with server requests in Redux…

— That’s because you don’t know shit about functional programming, idiot! It’s called “side-effect”.

See the strategy? Again, to be a good companion you have to remember many things as GitHub’s star ratio, latest comparison’s results and frameworks’ names after all.

Good luck

These simple advice will make you an outstanding Javascript developer in no time. You are smart enough, just follow them blindly.


Keep up! Star! Retweet!