-
-
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
Changes from all commits
45e77e6
6c12642
3827a5d
78dd416
0038d9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,8 @@ | |
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
* | ||
* @flow | ||
*/ | ||
/* eslint-disable max-len */ | ||
|
||
|
@@ -22,7 +24,7 @@ const NEWLINE_REGEXP = /\n/ig; | |
|
||
const getSymbols = Object.getOwnPropertySymbols || (obj => []); | ||
|
||
function isToStringedArrayType(toStringed) { | ||
function isToStringedArrayType(toStringed: string): boolean { | ||
return ( | ||
toStringed === '[object Array]' || | ||
toStringed === '[object ArrayBuffer]' || | ||
|
@@ -39,15 +41,15 @@ function isToStringedArrayType(toStringed) { | |
); | ||
} | ||
|
||
function printNumber(val) { | ||
function printNumber(val: number): string { | ||
if (val != +val) { | ||
return 'NaN'; | ||
} | ||
const isNegativeZero = val === 0 && (1 / val) < 0; | ||
return isNegativeZero ? '-0' : '' + val; | ||
} | ||
|
||
function printFunction(val, printFunctionName) { | ||
function printFunction(val: Function, printFunctionName: boolean): string { | ||
if (!printFunctionName) { | ||
return '[Function]'; | ||
} else if (val.name === '') { | ||
|
@@ -57,15 +59,15 @@ function printFunction(val, printFunctionName) { | |
} | ||
} | ||
|
||
function printSymbol(val) { | ||
function printSymbol(val: Symbol): string { | ||
return symbolToString.call(val).replace(SYMBOL_REGEXP, 'Symbol($1)'); | ||
} | ||
|
||
function printError(val) { | ||
function printError(val: Error): string { | ||
return '[' + errorToString.call(val) + ']'; | ||
} | ||
|
||
function printBasicValue(val, printFunctionName, escapeRegex) { | ||
function printBasicValue(val: any, printFunctionName: boolean, escapeRegex: boolean): StringOrNull { | ||
if (val === true || val === false) { | ||
return '' + val; | ||
} | ||
|
@@ -129,10 +131,10 @@ function printBasicValue(val, printFunctionName, escapeRegex) { | |
return printError(val); | ||
} | ||
|
||
return false; | ||
return null; | ||
} | ||
|
||
function printList(list, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors) { | ||
function printList(list: any, indent: string, prevIndent: string, spacing: string, edgeSpacing: string, refs: Refs, maxDepth: number, currentDepth: number, plugins: Plugins, min: boolean, callToJSON: boolean, printFunctionName: boolean, escapeRegex: boolean, colors: Colors): string { | ||
let body = ''; | ||
|
||
if (list.length) { | ||
|
@@ -154,15 +156,15 @@ function printList(list, indent, prevIndent, spacing, edgeSpacing, refs, maxDept | |
return '[' + body + ']'; | ||
} | ||
|
||
function printArguments(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors) { | ||
function printArguments(val: any, indent: string, prevIndent: string, spacing: string, edgeSpacing: string, refs: Refs, maxDepth: number, currentDepth: number, plugins: Plugins, min: boolean, callToJSON: boolean, printFunctionName: boolean, escapeRegex: boolean, colors: Colors): string { | ||
return (min ? '' : 'Arguments ') + printList(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors); | ||
} | ||
|
||
function printArray(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors) { | ||
function printArray(val: any, indent: string, prevIndent: string, spacing: string, edgeSpacing: string, refs: Refs, maxDepth: number, currentDepth: number, plugins: Plugins, min: boolean, callToJSON: boolean, printFunctionName: boolean, escapeRegex: boolean, colors: Colors): string { | ||
return (min ? '' : val.constructor.name + ' ') + printList(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors); | ||
} | ||
|
||
function printMap(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors) { | ||
function printMap(val: Map<any, any>, indent: string, prevIndent: string, spacing: string, edgeSpacing: string, refs: Refs, maxDepth: number, currentDepth: number, plugins: Plugins, min: boolean, callToJSON: boolean, printFunctionName: boolean, escapeRegex: boolean, colors: Colors): string { | ||
let result = 'Map {'; | ||
const iterator = val.entries(); | ||
let current = iterator.next(); | ||
|
@@ -191,14 +193,15 @@ function printMap(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, | |
return result + '}'; | ||
} | ||
|
||
function printObject(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors) { | ||
function printObject(val: Object, indent: string, prevIndent: string, spacing: string, edgeSpacing: string, refs: Refs, maxDepth: number, currentDepth: number, plugins: Plugins, min: boolean, callToJSON: boolean, printFunctionName: boolean, escapeRegex: boolean, colors: Colors): string { | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. I think we can safely remove this check: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean that 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 commentThe reason will be displayed to describe this comment to others. Learn more. Yup that's what I mean. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 :) |
||
.concat(symbols); | ||
} | ||
|
@@ -226,7 +229,7 @@ function printObject(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDep | |
return result + '}'; | ||
} | ||
|
||
function printSet(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors) { | ||
function printSet(val: Set<any>, indent: string, prevIndent: string, spacing: string, edgeSpacing: string, refs: Refs, maxDepth: number, currentDepth: number, plugins: Plugins, min: boolean, callToJSON: boolean, printFunctionName: boolean, escapeRegex: boolean, colors: Colors): string { | ||
let result = 'Set {'; | ||
const iterator = val.entries(); | ||
let current = iterator.next(); | ||
|
@@ -252,7 +255,7 @@ function printSet(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, | |
return result + '}'; | ||
} | ||
|
||
function printComplexValue(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors) { | ||
function printComplexValue(val: any, indent: string, prevIndent: string, spacing: string, edgeSpacing: string, refs: Refs, maxDepth: number, currentDepth: number, plugins: Plugins, min: boolean, callToJSON: boolean, printFunctionName: boolean, escapeRegex: boolean, colors: Colors): string { | ||
refs = refs.slice(); | ||
if (refs.indexOf(val) > -1) { | ||
return '[Circular]'; | ||
|
@@ -282,21 +285,19 @@ function printComplexValue(val, indent, prevIndent, spacing, edgeSpacing, refs, | |
return hitMaxDepth ? '[Object]' : printObject(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors); | ||
} | ||
|
||
function printPlugin(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors) { | ||
let match = false; | ||
function printPlugin(val, indent: string, prevIndent: string, spacing: string, edgeSpacing: string, refs: Refs, maxDepth: number, currentDepth: number, plugins: Plugins, min: boolean, callToJSON: boolean, printFunctionName: boolean, escapeRegex: boolean, colors: Colors): StringOrNull { | ||
let plugin; | ||
|
||
for (let p = 0; p < plugins.length; p++) { | ||
plugin = plugins[p]; | ||
|
||
if (plugin.test(val)) { | ||
match = true; | ||
if (plugins[p].test(val)) { | ||
plugin = plugins[p]; | ||
break; | ||
} | ||
} | ||
|
||
if (!match) { | ||
return false; | ||
if (!plugin) { | ||
return null; | ||
} | ||
|
||
function boundPrint(val) { | ||
|
@@ -316,21 +317,67 @@ function printPlugin(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDep | |
return plugin.print(val, boundPrint, boundIndent, opts, colors); | ||
} | ||
|
||
function print(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors) { | ||
const plugin = printPlugin(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors); | ||
if (plugin) { | ||
return plugin; | ||
function print(val: any, indent: string, prevIndent: string, spacing: string, edgeSpacing: string, refs: Refs, maxDepth: number, currentDepth: number, plugins: Plugins, min: boolean, callToJSON: boolean, printFunctionName: boolean, escapeRegex: boolean, colors: Colors): string { | ||
const pluginsResult = printPlugin(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors); | ||
if (typeof pluginsResult === 'string') { | ||
return pluginsResult; | ||
} | ||
|
||
const basic = printBasicValue(val, printFunctionName, escapeRegex); | ||
if (basic) { | ||
return basic; | ||
const basicResult = printBasicValue(val, printFunctionName, escapeRegex); | ||
if (basicResult !== null) { | ||
return basicResult; | ||
} | ||
|
||
return printComplexValue(val, indent, prevIndent, spacing, edgeSpacing, refs, maxDepth, currentDepth, plugins, min, callToJSON, printFunctionName, escapeRegex, colors); | ||
} | ||
|
||
const DEFAULTS = { | ||
type Colors = Object; | ||
type Indent = (str: string) => string; | ||
type Refs = Array<any>; | ||
type Serialize = (val: any) => string; | ||
type StringOrNull = string | null; // but disallow undefined, unlike ?string | ||
|
||
type Plugin = { | ||
print: (val: any, serialize: Serialize, indent: Indent, opts: Object, colors: Colors) => string, | ||
test: Function, | ||
} | ||
type Plugins = Array<Plugin>; | ||
|
||
type InitialOptions = {| | ||
callToJSON?: boolean, | ||
escapeRegex?: boolean, | ||
highlight?: boolean, | ||
indent?: number, | ||
maxDepth?: number, | ||
min?: boolean, | ||
plugins?: Plugins, | ||
printFunctionName?: boolean, | ||
theme?: { | ||
content: string, | ||
prop: string, | ||
tag: string, | ||
value: string, | ||
}, | ||
|}; | ||
|
||
type Options = {| | ||
callToJSON: boolean, | ||
escapeRegex: boolean, | ||
highlight: boolean, | ||
indent: number, | ||
maxDepth: number, | ||
min: boolean, | ||
plugins: Plugins, | ||
printFunctionName: boolean, | ||
theme: {| | ||
content: string, | ||
prop: string, | ||
tag: string, | ||
value: string, | ||
|}, | ||
|}; | ||
|
||
const DEFAULTS: Options = { | ||
callToJSON: true, | ||
escapeRegex: false, | ||
highlight: false, | ||
|
@@ -347,7 +394,7 @@ const DEFAULTS = { | |
}, | ||
}; | ||
|
||
function validateOptions(opts) { | ||
function validateOptions(opts: InitialOptions) { | ||
Object.keys(opts).forEach(key => { | ||
if (!DEFAULTS.hasOwnProperty(key)) { | ||
throw new Error('prettyFormat: Invalid option: ' + key); | ||
|
@@ -359,7 +406,7 @@ function validateOptions(opts) { | |
} | ||
} | ||
|
||
function normalizeOptions(opts) { | ||
function normalizeOptions(opts: InitialOptions): Options { | ||
const result = {}; | ||
|
||
Object.keys(DEFAULTS).forEach(key => | ||
|
@@ -370,19 +417,23 @@ function normalizeOptions(opts) { | |
result.indent = 0; | ||
} | ||
|
||
return result; | ||
// The type cast below means YOU are responsible to verify the code above. | ||
|
||
// $FlowFixMe object literal. Inexact type is incompatible with exact type | ||
return (result: Options); | ||
} | ||
|
||
function createIndent(indent) { | ||
function createIndent(indent: number): string { | ||
return new Array(indent + 1).join(' '); | ||
} | ||
|
||
function prettyFormat(val, opts) { | ||
if (!opts) { | ||
function prettyFormat(val: any, initialOptions?: InitialOptions): string { | ||
let opts: Options; | ||
if (!initialOptions) { | ||
opts = DEFAULTS; | ||
} else { | ||
validateOptions(opts); | ||
opts = normalizeOptions(opts); | ||
validateOptions(initialOptions); | ||
opts = normalizeOptions(initialOptions); | ||
} | ||
|
||
const colors = {}; | ||
|
@@ -405,13 +456,13 @@ function prettyFormat(val, opts) { | |
indent = createIndent(opts.indent); | ||
refs = []; | ||
const pluginsResult = printPlugin(val, indent, prevIndent, spacing, edgeSpacing, refs, opts.maxDepth, currentDepth, opts.plugins, opts.min, opts.callToJSON, opts.printFunctionName, opts.escapeRegex, colors); | ||
if (pluginsResult) { | ||
if (typeof pluginsResult === 'string') { | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Should these checks really be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I’m glad that you see and ask about this.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On second thought, what do you think about:
|
||
return basicResult; | ||
} | ||
|
||
|
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 ofany
.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:
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:
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.
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
vsany
clearly. So, for code that’s carefully written, rarely edited, and perf critical, we might leave well enough alone withany
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 aprintAmazingFeatureInES20xx
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 😕