Skip to content

[eslint-config] Proposal: Allow inferred types for local variables #2206

Closed
@octogonz

Description

@octogonz

Is this a feature or a bug?

  • Feature
  • Bug

We're converting a migrating a bunch of HBO code to use @rushstack/eslint-config, and in a number of cases it is awkward to have to declare types for local variables. The Rush Stack ruleset has required full type declarations for 5 years now, and lots and lots of code has been written using those rules.

Good reasons to be strict

Generally declaring types for local variables greatly improves readability. Compare this:

// Confusing!
function f(report: Report, owner: string): string[] {
    const results = [];

    const children = report.filter(true);
    for (const child of children) {
        const key = report.getKey(owner)
        const map = child.getByTag();
        const value = map.get(owner);
        if (value) {
            results.push(value.zip);
        }
    }
    return results;
}

with this:

// Better!
function f(report: Report, owner: string): string[] {
    const results: string[] = [];

    const children: Section[] = report.filter(true);
    for (const child of children) {
        const key: SectionId = report.getKey(owner)
        const map: Map<string, ICity> = child.getByTag();
        const value: ICity | undefined = map.get(owner);
        if (value) {
            results.push(value.zip);
        }
    }
    return results;
}

More examples and background are explained in the comments for the config.

Reasons to be a bit more lax

But typedef can be awkward for certain cases such as:

local data structures

function f(): void {
  // Nobody cares to describe the type of this, because it is not used outside this function.
  const samplePayload = { a: 1, b: 2 };

  webService.postJsonBlob(samplePayload);
}

algebraically composed types (that are not accessible outside their scope)

interface IThing {
  a: number;
  b: number;
}

function f(things: IThing[]): void {
  // This type is an ad hoc structure that would be cumbersome
  // to describe explicitly.  This sort of thing arises frequently in code
  // that processes JSON objects to produce reports (similar to SQL queries)
  const report = lodash.keyBy(things, 'a');  // { '1': { a: 1, b: 2 } }
  . . .
}

simple redundancies

This was a classic example where declarations are not always useful:

const map: Map<string, Promise<void>> = new Map<string, Promise<void>>();

(Although I eventually realized for this specific API you can get around it like this:)

const map: Map<string, Promise<void>> = new Map();

destructuring

It's also worth mentioning that the @rushstack/eslint-config ruleset today already makes a special exception for destructuring expressions on similar grounds:

interface IOptions {
    a: number;
    b: string;
    c: number;
}

function f(options: IOptions): void {
    // accepted already
    const {a, b} = options;
    
    // declaring the type often does not provide any actual new information
    // const {a, b}: IOptions = options;

    // and not destructuring does not provide new information either
    console.log(options.a);
    console.log(options.b);
}

Proposal

Let's follow the model of C# and allow type inference for local variables, whose signatures are never seen outside of the scope where they are used.

For code bases that are accustomed to requiring strict types everywhere, we can provide an "mix-in" import to restore the stricter behavior.

The motivation for explicit types is really about:

  • making code easier to read: being explicit is especially helpful for code reviewers looking at a Git diff (without fancy VS Code tooltips)
  • encouraging better software designs: For objects that are NOT local variables, explicitly declaring types prevents anonymous types, which makes developers go through the useful exercise of declaring an interface, choosing a name for it, and discussing its design in a code review

We can balance this with a couple counter goals:

  • encouraging adoption of our coding conventions - typedef is the rule people cite most when resisting our coding conventions
  • reducing lint suppressions for the edge cases pointed out above

This proposal arguably preserves the first two benefits, while adding the second two benefits.

@iclanton @hbo-iecheruo @KevinTCoughlin @johnguy0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Closed

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions