Skip to content

Treat assignments to properties on functions as valid declarations #15868

Closed
@DanielRosenwasser

Description

@DanielRosenwasser

Background

Something that newcomers run into as a result of migrating to TypeScript is that certain patterns don't work quite as easily anymore. For example, let's consider the following example:

export const Foo = props => <div>{props.name}</div>

Foo.propTypes = {
    // ...
};

This example doesn't work in TypeScript because propTypes isn't a valid property on Foo's type.

In the above case, there are two potential workarounds:

  1. Find/declare a type that exists that is callable and has a valid property:

    export const Foo: StatelessComponent<any> = props =>
        <div>
            {props.name}
        </div>
    
    Foo.propTypes = {
        // ...
    };

    This option has the benefit of being able to statically catch incorrectly named declarations, but is somewhat unattractive because of the placement. It also doesn't work well if the original declaration was a function declaration like so:

    export function Foo(props) {
        return <div>props.name</div>
    }

    Because it would need to be rewritten to a variable declaration to allow an annotation.

  2. Rewrite the const declaration to a function declaration and use namespace merging:

    export function Foo(props) {
        return <div>props.name</div>
    }
    
    namespace Foo {
         export let propTypes = { /*...*/ };
    }

    This option takes advantage of the namespace construct in TypeScript which performs value-merging; however, it is potentially worse because

    1. It requires the use of a construct that is fairly TypeScript specific, which may be unattractive to newcomers.
    2. It only works for function declarations. This requires a rewrite from any const declarations.

Unless there's a new construct which creates a callable object (cc @bterlson), then I want to discuss the option of recognizing property assignments on functions. In other words, the original example would simply be something that TypeScript could understand.

Proposal

For an effective function declaration named F at the top-level scope, the first assignment to a property p on F (i.e. F.p) is considered a declaration of a property named p on the type of F.

An effective function declaration can be described by a FunctionDeclaration or a VariableDeclaration whose initializer is

  1. a FunctionExpression
  2. an ArrowFunction
  3. a series of arbitrarily nested ParenthesiezdExpressions whose innermost expression is either of the above

Precedent

We already do this to some extent in checkJs mode because we simply have to. Not doing this would lead to an unacceptable experience there.

Drawbacks

  1. These declarations are significantly less explicit than other declarations, which makes the language less consisent.
  2. Type annotations cannot be placed on these declaration sites, whereas all other variable/property declarations allow a type annotation.
  3. Users may be confused by the positions where these declarations can occur.

Alternative Ideas

  1. Augmented variable declaration syntax

    let Foo.propTypes = { /*...*/ };
  2. Allow namespaces to merge with const/let/var declarations.

    export const Foo = props => <div>{props.name}</div>;
    export namespace Foo {
        export let propTypes = { /*...*/ };
    }
  3. Find a way to cleanly coerce the type of a function declaration.

Metadata

Metadata

Assignees

Labels

CommittedThe team has roadmapped this issueSuggestionAn idea for TypeScript

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions