-
Notifications
You must be signed in to change notification settings - Fork 736
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
Convert codebase to TypeScript #548
base: 2.0-take-2
Are you sure you want to change the base?
Conversation
This does look much better than I expected. I still don't like the little intermediate objects, but I think I can live with that. Let's do it BTW, you've been so helpful. Would you like member access? |
This does look much better than I expected. I still don't like the little
intermediate objects, but I think I can live with that. Let's do it
What do you mean by intermediate objects, the type definitions? Happy that
you like it, I'll keep on working on it.
BTW, you've been so helpful. Would you like member access?
I'd be honored, thanks for the offer. I'm starting a new gig next month,
and I don't know if I'll have a lot of spare time, but I'll be happy to
help.
Gilles
… |
I mean the type definitions for things we pass around inside Luxon, like |
7b93933
to
18ead49
Compare
This latest commit converts the whole code base to TS, and the ~700 initial error messages are gone. It mainly consists in adding types to all function parameters, and defining a few custom types along the way. I started from the work done by @mastermatt, @milesj and others on DefinitelyTyped. In some cases, I had to refactor some code to help TS understand it, other times I carefully added some Next steps:
Side note, with the new |
👏 |
src/datetime.ts
Outdated
@@ -109,67 +109,43 @@ function tsToObj(ts, offset) { | |||
minute: d.getUTCMinutes(), | |||
second: d.getUTCSeconds(), | |||
millisecond: d.getUTCMilliseconds() | |||
}; | |||
} as GregorianDateTime; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using as
here you could just set the return type on the function signature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I did initially. For consistency with the other methods, I then removed all the return types in function signatures (the only ones left are type guards).
If that's more idiomatic, I'll switch back to an explicit return type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consistency should rule all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, I reverted these and they are not identical.
Using return res as X
in a function will indeed define the return type of the function as X
. But it is brute force, and this as
is way too strong since one can write return 42 as string
.
On the other hand, specifying a return type on a method (when it cannot be properly inferred automatically) will also define the method's return type. But this time the compiler will simply check that the value returned indeed matches that type, which is what we usually want.
src/datetime.ts
Outdated
@@ -378,15 +333,20 @@ function lastOpts(argList) { | |||
* There's plenty others documented below. In addition, for more information on subtler topics like internationalization, time zones, alternative calendars, validity, and so on, see the external documentation. | |||
*/ | |||
export default class DateTime { | |||
// Private readonly fields | |||
private ts: Readonly<number>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was there a reason to use the Readonly
type over just making the attr readonly?
private readonly ts: number;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was done for consistency.
I understand primitives could support a readonly
while Objects need a Readonly<>
to have all their properties set to readonly.
I preferred to use the same keyword everywhere. Again, if this is not idiomatic, I'll be happy to fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GillesDebunne Sorry, but I think you misunderstood the meaning/usage of the Readonly<T>
Utility Type vs. the readonly
keyword...
The readonly
keyword is used to make a property readonly (but not it's value/content).
interface Bar { bazz: string }
class Foo {
readonly n: number = 21
readonly bar: Bar = { bazz: "foo" }
}
let foo = new Foo()
foo.n = 42 // <-- Error
foo.bar = { bazz: "bazz" } // <-- Error
foo.bar.bazz = "test" // <-- Working
The Readonly utility type (Readonly<T>
) on the other hand modifies all properties of the type T to become readonly - e.g. to represent what Object.freeze
does.
function freeze<T>(obj: T): Readonly<T>;
All Primitive values (like number
, string
, boolean
, ...) already are immutable - they can't be altered. This means applying Readonly<T>
to them (like Readonly<number>
or Readonly<string>
) has no effect at all!
interface Bar { bazz: string }
class Foo {
n: number = 21
i: Readonly<number> = 21 // makes no difference
s: string = "foo"
bar: Readonly<Bar> = { bazz: "foo" } // basically the same as:
bar2: { readonly bazz: string } = { bazz: "foo" }
}
let foo = new Foo()
foo.n = 42 // <-- Working
foo.i = 42 // <-- Working
foo.s = "bar" // <-- Working
foo.s.length = 1 // <-- Error
foo.bar = { bazz: "bazz" } // <-- Working
foo.bar.bazz = "test" // <-- Error
foo.bar2 = { bazz: "bazz" } // <-- Working
foo.bar2.bazz = "test" // <-- Error
So if you really want the whole thing to be readonly you need:
interface Bar { bazz: string }
class Foo {
readonly n: number = 21
readonly bar: Readonly<Bar> = { bazz: "foo" }
}
let foo = new Foo()
foo.n = 42 // <-- Error
foo.i = 42 // <-- Error
foo.bar = { bazz: "bazz" } // <-- Error
foo.bar.bazz = "test" // <-- Error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot, I thought I understood the difference between the two, and I chose to use Readonly
everywhere for consistency. I did not realize it did not work as expected on primitives. Will fix.
src/impl/util.ts
Outdated
@@ -52,65 +54,68 @@ export function hasRelative() { | |||
|
|||
// OBJECTS AND ARRAYS | |||
|
|||
export function maybeArray(thing) { | |||
export function maybeArray(thing: unknown) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export function maybeArray<T>(thing: T | ReadonlyArray<T>): T[] {
So the returned value is auto-typed based on the in the input.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well spotted.
Gilles, you can merge this whenever you're happy with it |
What's the release plan for this? |
This PR still needs some work. Making rollup understand TS was a breeze, but I now have 1000+ errors in the tests, which challenge my choices in types... The major feature left in the 2.0 roadmap is to extract the parser/formatter to reduce the library's footprint. |
@GillesDebunne I was thinking about that. I don't currently have the bandwidth to take on the formatter separation; things are very busy at work. So unless you want to take that on too, I'm starting to think we should just release 2.0 without it and then do it for 3.0, with a timeline of "whenever we make the change". Otherwise, my worry is that all these awesome changes already in 2.0 will lie around when they could be valuable now (or, whenever the typescript rework is done). |
This is a new iteration of this PR. This time, all the tests were migrated to TS. This resulted in some changes in the API types, and revealed a bug introduced while refactoring to TS. There are two breaking changes that need to be validated:
might indeed be surprising and confusing.
Next is a cleanup of the build process, so that it generates the |
The asymmetry between the getter and setter makes me want the explicit method more, not less. But why don't we just have both? For your second question, see #182 |
63940bd
to
f199fe9
Compare
New version of this PR. This time the goal was to get a green CI build. Fixed eslint config and errors, and built the docs from a transpiled version of the sources to ES6. Also (finally) created a new This is getting close. I need to remove all the |
I'm a little confused by the need for a types file. Seems like that would just come for free? |
It mostly does, but the output is made of several files that need to be merged. Some 'public' methods such as |
2b7421c
to
a03dfa2
Compare
@GillesDebunne I haven't been rebasing 2.0 to prevent this change from getting more complicated than it has to be, so I think the plan will be to merge this, then retrofit in the relevant changes from 1.x, and then release. |
This last commit removes the comments I had left around my changes. I double checked and reverted one. I'll take a look at the rebase from |
What’s the status on this? |
The branch needs a massive rebase on the current master.
I hope I will find time to do it, or someone can help.
…On Sat, Jan 25, 2020 at 8:42 PM kbradl16 ***@***.***> wrote:
What’s the status on this?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#548?email_source=notifications&email_token=AAOPFGTYBC76NNBEQSV67H3Q7SI3DA5CNFSM4IHRODMKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEJ5D5ZA#issuecomment-578436836>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAOPFGXONWMUQVATUO7UQNLQ7SI3DANCNFSM4IHRODMA>
.
|
18d7feb
to
1de2e41
Compare
|
This add support for 'setter' for Settings.defaultZone, The implementation reason is described here: moment/luxon#548 (comment) https://github.com/moment/luxon/blob/master/src/settings.js#L49 Thanks!
What is the status on this PR? |
I do not have much time to spend on this project. It would need to be rebased and adapted first. |
Ok, maybe I can help. Can you outline what is to be done (adapted)? |
A careful rebase is required to make sure the new code from |
Only rebase? Because your wrote something has to be adapted? So you mean only regarding the rebase? |
@GillesDebunne can I pick this from you? |
This is a Work In Progress.
Trying to move the code base to TypeScript.
Before I go any further, I would like your thoughts on how the code looks, and if you would be willing to merge such a PR.
There are a couple of TypeScript specific
Partial,Record
constructions, but the rest of the code should be fairly easy to read. I personally appreciate the safety it provides. It already helped to find a few minor issues (that I should translate to tests anyway).