-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
Add Flow type annotations to pretty-format #2977
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2977 +/- ##
==========================================
- Coverage 68.49% 68.47% -0.02%
==========================================
Files 146 146
Lines 5335 5333 -2
==========================================
- Hits 3654 3652 -2
Misses 1681 1681
Continue to review full report at Codecov.
|
const constructor = min ? '' : (val.constructor ? val.constructor.name + ' ' : 'Object '); | ||
let result = constructor + '{'; | ||
let keys = Object.keys(val).sort(); | ||
const symbols = getSymbols(val); | ||
|
||
if (symbols.length) { | ||
keys = keys | ||
// $FlowFixMe string literal `symbol`. This value is not a valid `typeof` return value | ||
.filter(key => !(typeof key === 'symbol' || toString.call(key) === '[object Symbol]')) |
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 we can safely remove this check: typeof key === 'symbol'
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.
You mean that .filter(key => toString.call(key) !== '[object Symbol]')
is enough?
I guess it is moving properties whose keys are symbols to follow the other.
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.
Yup that's what I mean. symbol
is indeed not a valid typeof return value and toString.call(symbol) works on Node 4.
Unless it's for perf, cc: @cpojer
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.
Aha, I thought I ran into this limitation of Flow before. It’s part of ES2015.
So the second part might be a fall-back? Notice:
const typeOf = typeof val;
if (typeOf === 'symbol') {
return printSymbol(val);
}
EDIT and also:
if (toStringed === '[object Symbol]') {
return printSymbol(val);
}
EDIT Will wait for second opinion. Adjusted existing test to emphasize correct sorting :)
👍 love your work! |
return pluginsResult; | ||
} | ||
} | ||
|
||
const basicResult = printBasicValue(val, opts.printFunctionName, opts.escapeRegex); | ||
if (basicResult) { | ||
if (basicResult !== null) { |
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.
Should these checks really be !== null
? Technically you are changing behavior here from what it was before. Should we do != null
to also catch undefined here?
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.
Yes, I’m glad that you see and ask about this.
-
Because
printBasicValue
doesn’t return undefined or empty string, there’s no change in behavior for the internal code path, as verified by tests. -
Because the 3 current built-in serializers don’t return those, no change there either.
-
The reason to recommend an intentional change in behavior now before many people write serializers, is to allow a serializer to return empty string.
exports[`Allow user-defined serializer to return empty string 1`] = ``
With current logic,
print
cannot distinguish no serializertest
method matches from a serializertest
method does match and itsprint
method returns empty string.Although a rare edge case, I ran into the problem in first attempt at diff serializer, when it returned empty string for no differences between prev and next state.
-
Yes, I agree that
!= null
is even better, in case a user-defined plugin returns undefined.
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.
On second thought, what do you think about:
if (basicResult !== null)
for the internal trusted code, because Flow will findundefined
and so onif (typeof plugin === 'string')
for the external untrusted code, in case a serializer returns anything other than a string. Does that test cause a performance problem?
packages/pretty-format/src/index.js
Outdated
min: boolean, | ||
plugins: Plugins, | ||
printFunctionName: boolean, | ||
theme: { |
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.
should this also use exact types?
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.
Yes, good catch.
In the next pull request, normalizeOptions
merges theme
from options argument with default theme
to fulfill the exact type. Now, an incomplete theme causes plugin to throw.
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.
Waiting for some comments by @pedrottimark
@pedrottimark thanks for the replies. I'm unclear whether you were going to make some changes to the code though? |
Yes, next commit will have changes:
|
Thanks for the update. Would you mind running the performance test suite before and after your change to know whether the typeof check affects performance at all? Try |
Hi, I’m Mark, and I write Totals of Short answer is no significant diff in average baseline 42.6025 ± 1.1% and current 42.532 ± 0.8%
|
I merged another change by @thejameskyle so I'm afraid I need to ask you to rebase one more time. I'm sorry. |
Oh good, now Yes, I will rebase carefully, so don’t hold your breath :) |
return '[' + errorToString.call(val) + ']'; | ||
} | ||
|
||
function printBasicValue(val, printFunctionName, escapeRegex) { | ||
function printBasicValue(val: any, printFunctionName: boolean, escapeRegex: boolean): StringOrNull { |
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.
Use mixed
instead of any
. any == unsafe
, mixed == 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.
Yes, thank you, in all 8 occurrences?
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.
Uh oh, just for the one change, 10 errors including:
function printBasicValue(val: mixed, printFunctionName: boolean, escapeRegex: boolean): StringOrNull {
if (val === true || val === false) {
return '' + val;
}
packages/pretty-format/src/index.js:72
72: return '' + val;
^^^ boolean literal `false`. This type cannot be added to
72: return '' + val;
^^ string
packages/pretty-format/src/index.js:72
72: return '' + val;
^^^ boolean literal `true`. This type cannot be added to
72: return '' + val;
^^ string
const typeOf = typeof val;
if (typeOf === 'number') {
return printNumber(val);
}
packages/pretty-format/src/index.js:84
84: return printNumber(val);
^^^ mixed. This type is incompatible with the expected param type of
44: function printNumber(val: number): string {
^^^^^^ 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.
Yup, this is what I would've expected. When I was writing pretty-format I did all sorts of micro-optimizations like storing typeof val
as a variable (which doesn't work as a refinement in Flow). Flow also requires things to be stringified in a certain way (i.e. '' + bool
is not considered valid).
We don't really want to modify the code of pretty-format by much because the optimizations do matter to make it as fast as JSON.stringify
.
The only remaining option would be to type cast values in different places:
const typeOf = typeof val;
if (typeOf === 'number') {
return printNumber(((val: any): number));
}
if (val === true || val === false) {
return '' + ((val: any): string);
}
These will then get stripped away, leaving the original code.
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.
Yes, thank you, in all 8 occurrences?
Yeah, you should try as much as possible to never use any
. It's as an opt-out from Flow's type checking. You should only use it in scenarios like above where Flow can't do what you need it to.
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.
Right, you explain mixed
vs any
clearly. So, for code that’s carefully written, rarely edited, and perf critical, we might leave well enough alone with any
in this PR?
For context, Jest had a regression in a serializer plugin recently, fixed in #2943, which Flow type checking would have prevented. Follow up includes adding enough checking to pretty-format
to protect against, for example, editing error in args or undefined return value if a printAmazingFeatureInES20xx
function is added.
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 not sure what you're asking 😕
* Add Flow type annotations to pretty-format * Move new test to be among existing tests * modify test of properties and symbols to emphasize correct sorting * exact type for theme and if plugin returns string
* Add Flow type annotations to pretty-format * Move new test to be among existing tests * modify test of properties and symbols to emphasize correct sorting * exact type for theme and if plugin returns string
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Summary
Add annotations to prevent problems when the code is edited to support new JavaScript features.
Reasons for minor changes to the code:
printPlugin
: removedmatch
because Flow thought thatplugin
could be undefined.printBasicValue
andprintPlugin
: returnnull
instead offalse
because Flow had to consider the possibility oftrue
value inif
statement following the call.print
andprettyFormat
: test explicitly not equal tonull
returned value to distinguish plugin not found from plugin returns empty string. Adding annotations confirmed subtle problem that I ran into a few months ago when initial attempt at a diff serializer returned empty string for no differences. Added a test to document expected behavior in this edge case.prettyFormat
: not assign toopts
argument because Flow no like change of type fromInitialOptions
with optional properties toOptions
with required properties.Only 2
$FlowFixMe
comments:normalizeOptions
: type cast(result: Options)
printObject
:typeof key === 'symbol'
Test plan
Type check and tests pass, including new test to verify an incorrect edge case is fixed.
I think
Map<any, any>
andSet<any>
are okay for CI because Node 4 has basic support.Future work
Colors type is unfinished because of edge cases with options in general and theme in specific.
If you agree with these complete annotations for function definitions to detect missing arguments or undefined return values, then I will complete the partial annotations in ReactTestComponent.