-
Notifications
You must be signed in to change notification settings - Fork 77
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
Migrate @fluent/bundle to TypeScript #436
Conversation
} | ||
|
||
if (arg instanceof FluentNumber) { | ||
return new FluentDateTime(arg.valueOf(), { ...arg.opts, ...values(opts) }); |
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.
FluentDateTime
extends FluentNumber
now, which made this possible. I needed a way to type-guard the access to .opts
, which is only guaranteed to exist on FluentNumber
and FluentDateTime
. Initially I just made two instanceof
checks here, but then decided that FluentNumber
and FluentDateTime
were close enough to make them related. FluentDateTime
keeps its value as a number, too.
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.
I'm having second thoughts about this.
In TypeScript, this works because Intl.DateTimeFormatOptions
happens to be assignable to Intl.NumberFormatOptions
. I'm not sure if we should rely on this.
On a higher level, this is a spec question: what should be the outcome of NUMBER($date)
and DATETIME($number)
? I think it will be safer to not make assumptions about the answer right now and wait for the formatting spec to be finished.
I'll revert the FluentDateTime extends FluentNumber
change.
@blushingpenguin, @huy-nguyen, @Seally -- would you be interested in taking a look at this PR and giving me hints about how to improve it? Thanks! |
I got to a point where this PR is now ready for review. I'd like to flag both @Pike for a general review, and @zbraniecki, who has typing experience from working on I added explicit return types to all functions and methods and I like how it improves the reading experience when browsing the code, in particular outside the IDE. I have a few naming questions; I asked them in inline comments. |
Some ad-hoc comments from the first phase of my review: I tried to map the syntax ebnf to ast.ts, and struggled for two reasons:
The latter might have been exaggerated by the fact that I was looking at expressions, which means everything is called Or at least I read that they read by "first few characters and last few characters and sum of characters inbetween". |
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.
stas and I talked about this a bit, keeping track of this in the PR:
- revert the behavior changes of
NUMBER
and friends on unused positional arguments - use plain Node names for classes in
ast.js
, as there's no ambiguity with@fluent/syntax
stas also suggested to rename the resolver functions from MessageReference
to something that actually resembles what the function does. I'm not sure if that makes this patch easier or harder to review, but the general idea makes sense to me.
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.
Only had time for a quick check, will look at it more later this week. Looks great so far, excited for a typed future! 🎉
fluent-bundle/src/resolver.ts
Outdated
if (!func) { | ||
scope.reportError(new ReferenceError(`Unknown function: ${name}()`)); | ||
return new FluentNone(`${name}()`); | ||
switch (name) { |
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.
I think I liked the old way better, but I guess the way TS does exports it's not as easy to achieve?
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.
Yeah, I was getting an error about typeof import(...)
not having an index signature. A workaround which I found was to use let func = scope.bundle._functions[name] || (<any>builtins)[name];
, which is shorter and much closer to the old way. Do you think it's a better choice here than the explicit switch?
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.
... or add NUMBER
and DATE
to _functions
in the bundle constructor?
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.
Yeah, I was getting an error about
typeof import(...)
not having an index signature. A workaround which I found was to uselet func = scope.bundle._functions[name] || (<any>builtins)[name];
, which is shorter and much closer to the old way. Do you think it's a better choice here than the explicit switch?
Yeah no I guess I agree that the switch is better than that, because we don't lose type info that way. Another option which imo beats switch would be sth like:
let func = scope.bundle._functions[name] || (
name == "DATETIME" || name == "NUMBER" ? builtins[name] : null
);
There should also be a more generic version to achieve the type refinement necessary to use name
as an index, but I'm not coming up with it rn 🤔
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.
I updated this PR to merge cleanly into the current master. |
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.
The ast is much more readable now, which has the side effect that I found a few more things.
General things I still need to learn is what TS types actually do, in terms of instanceof
. I wonder if we could get rid of the pseudo type type
.
Also, I'd like to learn more about the use of public
.
fluent-bundle/src/resolver.ts
Outdated
if (!func) { | ||
scope.reportError(new ReferenceError(`Unknown function: ${name}()`)); | ||
return new FluentNone(`${name}()`); | ||
switch (name) { |
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.
... or add NUMBER
and DATE
to _functions
in the bundle constructor?
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.
One nit question, one question more about understanding TS, but this looks good to me either way.
I have verified that |
See #376 for the discussion. As an experiment, I ported
@fluent/bundle
to TypeScript. Migrating the files insrc/
was a breeze; I did run into a few troubles with tests and packaging, however.Summary
src/
are now written in TypeScript.make compile
compiles them into corresponding JS files inesm/
.tsc
is a project-based compiler which compiles many files at once. While it's possible to run it on a file-by-file basis, it's slower to do so and some features of TS won't work (e.g.const enum
). I added an empty file atesm/.compiled
which is touched every timetsc
runs to indicate tomake
that the compiled files are up to date compared to the source insrc
.esm/
directory, which means that the project needs to be compiled before tests are run.make test
takes care of that automatically.src
at the moment when the tests are run.esm
folder rather than using one tool to compile them for shipping (tsc
) and another to run tests (@babel/preset-typescript
).typedoc
to produce API docs.tsc
'starget: "es2017"
to produce thecompat.js
build, but in the end decided that it would be best to limit the number of moving parts in the PR.compat.js
build is still produced with Rollup and Babel, as it used to be.tsc
'starget: "es2017"
.Work Remaining
API Changes
FluentType
class toFluentBaseType
, and then I re-introducedFluentType
as a type alias in TS:type FluentType = string | FluentBaseType
. I'm not sure about the naming, however, and I'm open to alternatives.FluentError
. It was only used in the runtime parser. In the resolver we use native errors:RangeError
,ReferenceError
, andTypeError
, so I changed all uses ofFluentError
to the nativeSyntaxError
.