It is possible to prevent closure from capturing external variable in TypeScript?

Viewed 398

Suppose I have the following Typescript code:

const x = 123
const f = () => x 

Is it possible to do something such that Typescript compiler can produce an error/warning during compile time saying that function f is capturing an external variable x ?

P/S: I am solely looking for the technical possibility of this mechanism, explanation of why I should not do this will not be accepted as answer.

3 Answers

What you are looking for is not some configuration for a compiler, but for a linter.

See ESLint, e.g. and rules like no-shadow:

Shadowing is the process by which a local variable shares the same name as a variable in its containing scope. This rule aims to eliminate shadowed variable declarations.

The reason why it is not in a compiler infra - too much responsibility. Compiler is in charge for parsing the source code and producing meaningful target code, which is not an easy task. Checking the readability, enforcing best practices, etc. will be too much for it. That is why there are tools like linters, that enforces rules in your code that in turn enforce readability practices etc.


UPD: I understood the question wrong, OP is not about shadowing a variable, but using the variable from upper scope.

What comes to my mind to achieve this is to write your own plugin for linter that will implement such a behaviour.

E.g. you can implement your own rule for ESLint - reference to developer guide.

So the idea is that you implement your own rule (function) that must return a Visitor for AST nodes. By visiting the AST of the code you can manipulate the information, store the scopes where each of the variables are referenced to and check if someone is not trying to access it from another scope (that is a short version).

Taking Eugene's advice, I wrote an eslint plugin to do exactly this. If you install and configure the plugin, the following code:

const x = 123
// eslint-no-closure
const f = () => x 

will generate:

file.ts
  1:7   error  declared variable x referenced in an `eslint-no-closure` function  no-closure/no-tagged-closures
  3:11  error  function tagged with `eslint-no-closure` closes variables: x       no-closure/no-tagged-closures
  3:17  error  reference to variable x in an `eslint-no-closure` function         no-closure/no-tagged-closures

āœ– 3 problems (3 errors, 0 warnings)

The default reporting is a little verbose, but it can be paired down.

I'd write f in a separate file, that way no other variables from the original file will be lexically visible. This works just fine in Javascript too:

// main.ts
import { f } from './f'
const x = 123;
// do stuff with f and x
// f.ts
export const f = () => {
  console.log('running f');
  // trying to reference x here (or anything from the original file)
  // will throw a TS error
};
Related