10

I have a very simple example where TypeScript (3.5.1) is fine with the code, but it immediately throws an error when run.

I believe the issue here is that essentially value is declared but not initialized prior to getValue running. This is pretty unintuitive imo but I understand this is how JS works.

However, why can't TS detect this issue in such a simple example? Since value is a const, it seems to me TS should be able to determine exactly when it's set and predict that this code crashes.

console.log(getValue());

const value = "some string";

function getValue() {
  return value;
}

In a second example without a function call, TS does catch that the variable is used before assignment:

console.log(value);
const value = "some string";

TSLint's no-use-before-declare also does not appear applicable.

Assuming TS/linting will not be able to catch this, is there a best practice to apply in the initial example that will avoid this crash? "Always declare module-level consts at top of file" for example.

Freewalker
  • 3,608
  • 1
  • 30
  • 52
  • My answer would be "test". Type system and language can only capture some categories of error. Same as you can't expect a program would not crash if it compiles in statically typed language such as Java/C#. – unional May 29 '19 at 21:55
  • 3
    This happens due to hoisting in JS (function is hoisted, const - not). It seems that ts does not checks such things as hoisting. You may look at https://github.com/Microsoft/TypeScript/issues/19819 This issue also with hoisting, but nothing has been suggested – Fyodor May 29 '19 at 23:06
  • 1
    I think it's hard for static analysis to accurately detect [temporal dead zone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone) errors. It looks like TypeScript generally [prefers false negatives to false positives](https://github.com/microsoft/TypeScript/issues/4244#issuecomment-129591399) for this kind of thing. – jcalz May 30 '19 at 00:29
  • Also see [Microsoft/TypeScript#13638](https://github.com/microsoft/TypeScript/issues/13638) – jcalz May 30 '19 at 00:33
  • interesting. Even `no-use-before-declare` rule in tslint didn't catch that. –  May 30 '19 at 03:09

2 Answers2

3

You could enable tslint's only-arrow-functions and then replace your function getValue() with

const getValue = function(): string {
  return value;
}

or even

const getValue = (): string => value;

At that point your first line will be a compiler error:

Block-scoped variable 'getValue' used before its declaration
danielnixon
  • 3,845
  • 1
  • 22
  • 37
  • "Always use lambda" looks like a reasonable solution. Previously we were always using the `function` keyword at top level (global/module scope) primarily to get better call stacks. (See https://stackoverflow.com/a/23045200/152711) But on testing now, node (ts-node in this case) actually gives us call stacks which include named lambdas like `const someLambda = () => null;`. So I don't think there's a compelling reason to use `function` keyword at all for our purposes. I also like that using `const` not `function` keeps our files organized consistently. – Freewalker May 30 '19 at 16:12
0

I think the arrow function answer above answers your question the best, but just as a sidenote: deciding on a strict workflow will also prevent hoisting errors: declare vars, declare functions, call init function:

const value = "some string"

function startApp() { 
    console.log(getValue());
}

function getValue() {
    console.log("yo " + value)
    return value;
}

startApp()
Kokodoko
  • 19,176
  • 21
  • 88
  • 153