New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring to convert to "named parameters" #23552

Open
DanielRosenwasser opened this Issue Apr 19, 2018 · 17 comments

Comments

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Apr 19, 2018

It would look something like this

converttonamedparameters

@DanielRosenwasser

This comment has been minimized.

Copy link
Member Author

DanielRosenwasser commented Apr 19, 2018

CC @rauschma @appsforartists in case you want to 👍

@DanielRosenwasser DanielRosenwasser changed the title Refactoring to convert to named parameters Refactoring to convert to "named parameters" Apr 19, 2018

@rauschma

This comment has been minimized.

Copy link

rauschma commented Apr 20, 2018

Apologies for being ungrateful: this doesn’t reflect how I use this pattern – I don’t see the parameters as a whole, I see each parameter individually. As a work-around, I inline the type and don’t define it externally.

Let me try to convince you, one last time (then I’ll stop pestering you), of the usefulness of a better notation for destructuring (nested destructuring will profit, too!). The following is an extreme example, but there are many similar functions in my code (“Showoff” is the name of my slide framework):

function slidesToNodeTree(
  {conf, configShowoff, inputDir, slideDeckDir, slideFileName, parentPartNode, visitedSlideFiles}
  : {conf: ConfigSlideLink, configShowoff: ConfigShowoff, inputDir: string,
    slideDeckDir: ServePath, slideFileName: string, parentPartNode: PartNode,
    visitedSlideFiles: SlideFileDesc[]}) {
  ···
}

With a better notation:

function slidesToNodeTree(
  { conf as ConfigSlideLink, configShowoff as ConfigShowoff, inputDir as string,
    slideDeckDir as ServePath, slideFileName as string, parentPartNode as PartNode,
    visitedSlideFiles as SlideFileDesc[]}) {
  ···
}

Alternatively:

function slidesToNodeTree(
  { (conf: ConfigSlideLink), (configShowoff: ConfigShowoff), (inputDir: string),
    (slideDeckDir: ServePath), (slideFileName: string), (parentPartNode: PartNode),
    (visitedSlideFiles: SlideFileDesc[])}) {
  ···
}

Note how, in the last two examples, you actually see parameters with names (vs. a single type for all parameters). The first example looks messy, the last two examples don’t.

If you have ever used a programming language with named parameters and liked them there – isn’t this a compelling use case? Given the thumbs-up at a recent comment of mine, there are quite a few people who agree.

This is the only aspect of my plain JavaScript code that became less usable after I moved to TypeScript.

@Kingwl

This comment has been minimized.

Copy link
Collaborator

Kingwl commented Apr 20, 2018

a useful feature 👍

@appsforartists

This comment has been minimized.

Copy link

appsforartists commented Apr 20, 2018

I love that you're exploring this, but I agree with @rauschma.

I think there's a bit of a cargo-cult bias in the TS community to always prefer interfaces (because they can be extended). Type literals seem like a more appropriate model for the kwargs pattern, because they allow users to treat each named argument individually.

If a third party made a "convert to named arguments" command, I would probably find it useful to be able to write function signatures normally and then convert them to an interface/type literal. However, I'd rather the language supported setting the type for a named arg adjacent to the declaration of its name and default value. It's both hard to read and cumbersome to maintain when the types are separated from the rest of the definition.

@cancerberoSgx

This comment has been minimized.

Copy link

cancerberoSgx commented May 21, 2018

This refactor also must refactor all references to the method in the whole project right ? But a very helpful refactor , in my experience, happened many times when an API signature that needs to be backwards compatible, is defined with multiple params and then you keep adding parameters to implement new features or even change parameter type to OR, like existing: PreviousType|NewSemanticType....

@Kingwl

This comment has been minimized.

Copy link
Collaborator

Kingwl commented Jun 19, 2018

This refactor also must refactor all references to the method in the whole project right ?

IMO, it should

@cancerberoSgx

This comment has been minimized.

Copy link

cancerberoSgx commented Jun 19, 2018

So I was playing a lot lately with Language Service APIs and friends and I have several refactors more or less working fine. This is the one suggested here (I think) :

https://github.com/cancerberoSgx/typescript-plugins-of-mine/tree/master/typescript-plugin-proactive-code-fixes#transform-parameter-list-into-single-object-parameter

You can easily install them in vscode as an extension: https://marketplace.visualstudio.com/items?itemName=cancerberosgx.vscode-typescript-refactors

Probably TypeScript team will implement these more elegantly but in the meanwhile at least is fun. This is kind of a crazy tool that was really helpful to develop plugins quickly and learn the API: https://github.com/cancerberoSgx/typescript-plugins-of-mine/blob/master/typescript-plugin-ast-inspector/doc/evalCodeTutorial.md

And now I'm playing with some IPC communication between plugins in tsserver and host editor plugins /
extensions / packages that provide generic input-related operations - so I can inquire user for data in my refactors visually and keep being editor / IDE agnostic. For example, if I want to move a method to another class, or to another file I need to ask the user the destination and for that plugins can talk with these "Input Providers" generically. Since I'm manipulating the SourceFiles myself (not using FileEditRange ) I can do it synchronously. Really enjoying it will update you when I have something pretty to show.

@mohsen1

This comment has been minimized.

Copy link
Contributor

mohsen1 commented Dec 5, 2018

This is great!

Have you thought about the default arguments with no types? Would you infer types there?

function foo(a = 1, b?: string) 

Also would it work in constructor function with private or public keyword? I think it won't, right?

class Foo {
  constructor(private a: string) {}
}

where would you generate a type name if function is anonymous?

(function (a = 1, b?: string){}).call(1)
@AnyhowStep

This comment has been minimized.

Copy link
Contributor

AnyhowStep commented Dec 14, 2018

This would be super helpful to me, even if others would rather a new destructuring syntax for function parameters.

So, if the proposed refactoring somehow loses favour over new destructuring syntax, I would still like for this particular one to be implemented.

I personally use interfaces instead of inlining object parameter types, especially for large projects. And projects I write that get consumed by others.

I've had to wrap a function many times. And if the library doesn't have those function arguments as an interface, then I have to copy-paste their inlined code instead of just using an interface that should already be there.

People like to think of interfaces as being "code duplication". But in the big picture, not having those interfaces causes code duplication.


So, in my personal opinion, inlining = save time now, but cause code duplication in the future/for downstream users of your library. Interface = a little extra time now, but reduce code duplication and make it easier for downstream users to extend/reuse/wrap code.

@DanielRosenwasser

This comment has been minimized.

Copy link
Member Author

DanielRosenwasser commented Dec 17, 2018

@mohsen1 you're now my favorite QA contact for refactorings 😉

Have you thought about the default arguments with no types? Would you infer types there?

Good test case! Should "fall out" from the naive implementation.

Also would it work in constructor function with private or public keyword? I think it won't, right?

It probably should not for now.

where would you generate a type name if function is anonymous?

As a first-pass, it's probably reasonable to say that this would only generate a named type for

  1. Function declarations
  2. Single-variable declarations initialized with some sort of function expression/arrow function
  3. Constructors without parameter properties
  4. Method declarations

We could always come back to this and add it for signatures like call type literals, construct type literals, call/construct signatures, and method signatures (i.e. the ambient stuff).

@Kingwl

This comment has been minimized.

Copy link
Collaborator

Kingwl commented Jan 10, 2019

ummmmmm, I'd like to work on it (If I didn't disrupt your release plan)

@aleksey-bykov

This comment has been minimized.

Copy link

aleksey-bykov commented Jan 10, 2019

Options your say, why not Params?

@DanielRosenwasser

This comment has been minimized.

Copy link
Member Author

DanielRosenwasser commented Jan 10, 2019

Params is fine too - the important thing is that the refactoring provides a rename location on the generated type so that editors can immediately start a rename session.

@Kingwl this one might involve more work than other refactorings, but nobody's started working on it yet, so go for it!

@tentreescantbeatme

This comment has been minimized.

Copy link

tentreescantbeatme commented Jan 14, 2019

@rauschma then you have two syntaxes to be maintained. Because your version is not compatible with the current version that allows sharing of interfaces between methods. So you get an inconsistency there, because you can't just abolish the current syntax because of exactly this reason.

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

RyanCavanaugh commented Jan 14, 2019

@Kingwl we've decided to have our intern @gabritto implement this for her project - apologies if you've already started on it

@Kingwl

This comment has been minimized.

Copy link
Collaborator

Kingwl commented Jan 15, 2019

@RyanCavanaugh
Please feel free, Actually, I haven't started that work

@DanielRosenwasser

This comment has been minimized.

Copy link
Member Author

DanielRosenwasser commented Jan 15, 2019

For the record, I'd like to not continue discussion around new syntax here. This feature could theoretically work with that syntax anyway so it shouldn't have any bearing on the implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment