diff --git a/package.json b/package.json index 93e43ebc..30da5248 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "ast-types": "0.9.8", "babel-code-frame": "6.22.0", - "babylon": "7.0.0-beta.4", + "babylon": "7.0.0-beta.8", "chalk": "1.1.3", "esutils": "2.0.2", "flow-parser": "0.40.0", diff --git a/tests/flow/predicates-abstract/__snapshots__/jsfmt.spec.js.snap b/tests/flow/predicates-abstract/__snapshots__/jsfmt.spec.js.snap index 80832bdb..7c4fa594 100644 --- a/tests/flow/predicates-abstract/__snapshots__/jsfmt.spec.js.snap +++ b/tests/flow/predicates-abstract/__snapshots__/jsfmt.spec.js.snap @@ -34,12 +34,87 @@ function is_string(x): %checks { " `; +exports[`filter.js 2`] = ` +"// @flow + +// Filter the contents of an array + +declare function my_filter>(v: Array, cb: P): Array<$Refine>; + +declare var arr: Array; +const barr = my_filter(arr, is_string); +(barr: Array); + +function is_string(x): %checks { + return typeof x === \\"string\\"; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Filter the contents of an array + +declare function my_filter>( + v: Array, + cb: P +): Array<$Refine>; + +declare var arr: Array; +const barr = my_filter(arr, is_string); +(barr: Array); + +function is_string(x): %checks { + return typeof x === \\"string\\"; +} +" +`; + exports[`filter-union.js 1`] = ` "// @flow // Filter the contents of an array +declare function my_filter>(v: Array, cb: P): Array<$Refine>; + +type A = { kind: 'A', u: number } +type B = { kind: 'B', v: string } +type C = { kind: 'C', y: boolean } +type D = { kind: 'D', x: boolean } +type E = { kind: 'E', y: boolean } + +declare var ab: Array; + +(my_filter(ab, (x): %checks => x.kind === 'A'): Array); // OK +(my_filter(ab, (x): %checks => x.kind !== 'A'): Array); // OK +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Filter the contents of an array + +declare function my_filter>( + v: Array, + cb: P +): Array<$Refine>; + +type A = { kind: \\"A\\", u: number }; +type B = { kind: \\"B\\", v: string }; +type C = { kind: \\"C\\", y: boolean }; +type D = { kind: \\"D\\", x: boolean }; +type E = { kind: \\"E\\", y: boolean }; + +declare var ab: Array; + +(my_filter(ab, (x): %checks => x.kind === \\"A\\"): Array); // OK +(my_filter(ab, (x): %checks => x.kind !== \\"A\\"): Array); // OK +" +`; + +exports[`filter-union.js 2`] = ` +"// @flow + +// Filter the contents of an array + + declare function my_filter>(v: Array, cb: P): Array<$Refine>; type A = { kind: 'A', u: number } @@ -110,6 +185,106 @@ var e = refine2(c, d, is_string_and_number); (e: string); +declare function refine2>(v: T, w: T, cb: P): $Refine; + +// function refine_fst(v, w, cb) +// { if (cb(v, w)) { return w; } else { throw new Error(); } } + +function is_string(x): boolean %checks { + return typeof x === \\"string\\"; +} + +function is_string_and_number(x, y): %checks { + return typeof x === \\"string\\" && typeof y === \\"number\\"; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +/* + $Pred is an \\"abstract predicate type\\", i.e. denotes a (function) type that + refines N variables. So if \`cb\` is a function, then it should be refining + exactly N argument. It is abstract in that we do not need to specify: + (a) which variables are going to be refined (just the number), or (b) what + exactly the refinement (predicate) is going to be. + + $Refine is a refinement type, that refines type T with the k-th + argument that gets refined by an abstract preficate type P. +*/ +declare function refine>(v: T, cb: P): $Refine; +// function refine(v, cb) +// { if (cb(v)) { return v; } else { throw new Error(); } } + +/* + Use case +*/ +declare var a: mixed; +var b = refine(a, is_string); +(b: string); + +declare function refine_fst>( + v: T, + w: T, + cb: P +): $Refine; +// function refine_fst(v, w, cb) +// { if (cb(v, w)) { return v; } else { throw new Error(); } } + +declare var c: mixed; +declare var d: mixed; + +var e = refine2(c, d, is_string_and_number); +(e: string); + +declare function refine2>(v: T, w: T, cb: P): $Refine; + +// function refine_fst(v, w, cb) +// { if (cb(v, w)) { return w; } else { throw new Error(); } } + +function is_string(x): boolean %checks { + return typeof x === \\"string\\"; +} + +function is_string_and_number(x, y): %checks { + return typeof x === \\"string\\" && typeof y === \\"number\\"; +} +" +`; + +exports[`refine.js 2`] = ` +"// @flow + +/* + $Pred is an \\"abstract predicate type\\", i.e. denotes a (function) type that + refines N variables. So if \`cb\` is a function, then it should be refining + exactly N argument. It is abstract in that we do not need to specify: + (a) which variables are going to be refined (just the number), or (b) what + exactly the refinement (predicate) is going to be. + + $Refine is a refinement type, that refines type T with the k-th + argument that gets refined by an abstract preficate type P. +*/ +declare function refine>(v: T, cb: P): $Refine; +// function refine(v, cb) +// { if (cb(v)) { return v; } else { throw new Error(); } } + +/* + Use case +*/ +declare var a: mixed; +var b = refine(a, is_string); +(b: string); + +declare function refine_fst>(v: T, w: T, cb: P): $Refine; +// function refine_fst(v, w, cb) +// { if (cb(v, w)) { return v; } else { throw new Error(); } } + +declare var c: mixed; +declare var d: mixed; + +var e = refine2(c, d, is_string_and_number); +(e: string); + + declare function refine2>(v: T, w: T, cb: P): $Refine; // function refine_fst(v, w, cb) @@ -186,6 +361,57 @@ const b = my_filter(a, is_string); (b: Array); +// Sanity check B: Passing non-predicate function to filter +declare var c: Array; +const d = my_filter(c, is_string_regular); +(d: Array); + +function is_string(x): boolean %checks { + return typeof x === \\"string\\"; +} + +function is_string_regular(x): boolean { + return typeof x === \\"string\\"; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +declare function my_filter>( + v: Array, + cb: P +): Array<$Refine>; + +// Sanity check A: filtering the wrong type +declare var a: Array; +const b = my_filter(a, is_string); +(b: Array); + +// Sanity check B: Passing non-predicate function to filter +declare var c: Array; +const d = my_filter(c, is_string_regular); +(d: Array); + +function is_string(x): boolean %checks { + return typeof x === \\"string\\"; +} + +function is_string_regular(x): boolean { + return typeof x === \\"string\\"; +} +" +`; + +exports[`sanity-filter.js 2`] = ` +"// @flow + +declare function my_filter>(v: Array, cb: P): Array<$Refine>; + +// Sanity check A: filtering the wrong type +declare var a: Array; +const b = my_filter(a, is_string); +(b: Array); + + // Sanity check B: Passing non-predicate function to filter declare var c: Array; const d = my_filter(c, is_string_regular); @@ -232,6 +458,47 @@ exports[`sanity-filter-union.js 1`] = ` // Filter the contents of an array +declare function my_filter>(v: Array, cb: P): Array<$Refine>; + +type A = { kind: 'A', u: number } +type B = { kind: 'B', v: string } +type C = { kind: 'C', y: boolean } +type D = { kind: 'D', x: boolean } +type E = { kind: 'E', y: boolean } + +declare var ab: Array; + +(my_filter(ab, (x): %checks => x.kind === 'A'): Array); // ERROR +(my_filter(ab, (x): %checks => x.kind !== 'A'): Array); // ERROR +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Filter the contents of an array + +declare function my_filter>( + v: Array, + cb: P +): Array<$Refine>; + +type A = { kind: \\"A\\", u: number }; +type B = { kind: \\"B\\", v: string }; +type C = { kind: \\"C\\", y: boolean }; +type D = { kind: \\"D\\", x: boolean }; +type E = { kind: \\"E\\", y: boolean }; + +declare var ab: Array; + +(my_filter(ab, (x): %checks => x.kind === \\"A\\"): Array); // ERROR +(my_filter(ab, (x): %checks => x.kind !== \\"A\\"): Array); // ERROR +" +`; + +exports[`sanity-filter-union.js 2`] = ` +"// @flow + +// Filter the contents of an array + + declare function my_filter>(v: Array, cb: P): Array<$Refine>; type A = { kind: 'A', u: number } @@ -294,6 +561,102 @@ function is_string_and_number(x, y): %checks { } +// Sanity check C: expecting a predicate function but passed a non-predicate one +var e = refine(a, is_string_regular); // ERROR: is_string_regular is not a + // predicate function +(e: number); + +//////////////////////////////////////////////////////////////////////////////// + +function is_string(x): %checks { + return typeof x === \\"string\\"; +} + +function is_string_regular(x) { + return typeof x === \\"string\\"; +} + +function is_string_and_number(x, y): %checks { + return typeof x === \\"string\\" && typeof y === \\"number\\"; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Sanity check A: the refinment position index is outside of the allowed range +declare function refine>(v: T, cb: P): $Refine; + +declare var a: mixed; +var b = refine(a, is_string); // ERROR: index out of bounds +(b: string); + +// Sanity check B: refine2 expects a function that accepts 3 arguments but +// it is called with a function that takes 2 +declare var c: mixed; +declare var d: mixed; +declare var e: mixed; + +declare function refine3>( + u: T, + v: T, + w: T, + cb: P +): $Refine; + +var e = refine3(c, d, e, is_string_and_number); +(e: string); + +function is_string_and_number(x, y): %checks { + return typeof x === \\"string\\" && typeof y === \\"number\\"; +} + +// Sanity check C: expecting a predicate function but passed a non-predicate one +var e = refine(a, is_string_regular); // ERROR: is_string_regular is not a +// predicate function +(e: number); + +//////////////////////////////////////////////////////////////////////////////// + +function is_string(x): %checks { + return typeof x === \\"string\\"; +} + +function is_string_regular(x) { + return typeof x === \\"string\\"; +} + +function is_string_and_number(x, y): %checks { + return typeof x === \\"string\\" && typeof y === \\"number\\"; +} +" +`; + +exports[`sanity-refine.js 2`] = ` +"// @flow + +// Sanity check A: the refinment position index is outside of the allowed range +declare function refine>(v: T, cb: P): $Refine; + +declare var a: mixed; +var b = refine(a, is_string); // ERROR: index out of bounds +(b: string); + + +// Sanity check B: refine2 expects a function that accepts 3 arguments but +// it is called with a function that takes 2 +declare var c: mixed; +declare var d: mixed; +declare var e: mixed; + +declare function refine3>(u: T, v: T, w: T, cb: P): $Refine; + +var e = refine3(c, d, e, is_string_and_number); +(e: string); + +function is_string_and_number(x, y): %checks { + return typeof x === \\"string\\" && typeof y === \\"number\\"; +} + + // Sanity check C: expecting a predicate function but passed a non-predicate one var e = refine(a, is_string_regular); // ERROR: is_string_regular is not a // predicate function diff --git a/tests/flow/predicates-abstract/jsfmt.spec.js b/tests/flow/predicates-abstract/jsfmt.spec.js index 989047bc..5399f5ff 100644 --- a/tests/flow/predicates-abstract/jsfmt.spec.js +++ b/tests/flow/predicates-abstract/jsfmt.spec.js @@ -1 +1,2 @@ run_spec(__dirname); +run_spec(__dirname, { parser: "babylon" }); diff --git a/tests/flow/predicates-declared/__snapshots__/jsfmt.spec.js.snap b/tests/flow/predicates-declared/__snapshots__/jsfmt.spec.js.snap index 50dc606c..1fcc35fe 100644 --- a/tests/flow/predicates-declared/__snapshots__/jsfmt.spec.js.snap +++ b/tests/flow/predicates-declared/__snapshots__/jsfmt.spec.js.snap @@ -30,6 +30,92 @@ if (m.bind(o)) { } +class D { + m: Function; + + n() { + if(this.m({})) { } + } +} + +declare var m: Function; +const x = \\"\\"; +if (m.bind(this)(x)) { } +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Sanity checks: +// - use of bind in a position of a function predicate. +// (This case should fall through, as method calls +// are currently not supported.) The original behavior +// (including \`havoc\`) should be retained. + +class C { + m() { + return true; + } + a: 1; + + n() { + if (this.m.bind(this)) { + this.a; + } + } +} + +declare var m: Function; +const o = { a: 1 }; + +if (m.bind(o)) { + o.a; +} + +class D { + m: Function; + + n() { + if (this.m({})) { + } + } +} + +declare var m: Function; +const x = \\"\\"; +if (m.bind(this)(x)) { +} +" +`; + +exports[`function-bind.js 2`] = ` +"// @flow + +// Sanity checks: +// - use of bind in a position of a function predicate. +// (This case should fall through, as method calls +// are currently not supported.) The original behavior +// (including \`havoc\`) should be retained. + +class C { + m() { + return true; + } + a: 1; + + n() { + if(this.m.bind(this)) { + this.a; + } + } +} + +declare var m: Function; +const o = { a: 1 }; + +if (m.bind(o)) { + o.a; +} + + class D { m: Function; @@ -126,6 +212,46 @@ function foo(x: number | string | Array): number { " `; +exports[`function-union.js 2`] = ` +"// @flow + +declare function f1(x: mixed): boolean %checks(typeof x === \\"string\\"); +declare function f2(x: mixed): boolean %checks(Array.isArray(x)); + +declare var cond: boolean; + +// Feature check: +function foo(x: number | string | Array): number { + + var f = (cond) ? f1 : f2; + + if (f(x)) { + return x.length; + } else { + return 1; + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +declare function f1(x: mixed): boolean %checks(typeof x === \\"string\\"); +declare function f2(x: mixed): boolean %checks(Array.isArray(x)); + +declare var cond: boolean; + +// Feature check: +function foo(x: number | string | Array): number { + var f = cond ? f1 : f2; + + if (f(x)) { + return x.length; + } else { + return 1; + } +} +" +`; + exports[`is-string-decl.js 1`] = ` "// @flow @@ -167,6 +293,47 @@ function foo(x: string | Array): string { " `; +exports[`is-string-decl.js 2`] = ` +"// @flow + +declare function is_string(x: mixed): boolean %checks(typeof x === \\"string\\"); +declare function is_number(x: mixed): boolean %checks(typeof x === \\"number\\"); + +// Feature check: +function foo(x: string | Array): string { + if (is_string(x)) { + // The use of \`is_string\` as a conditional check + // should guarantee the narrowing of the type of \`x\` + // to string. + return x; + } else { + // Accordingly the negation of the above check + // guarantees that \`x\` here is an Array + return x.join(); + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +declare function is_string(x: mixed): boolean %checks(typeof x === \\"string\\"); +declare function is_number(x: mixed): boolean %checks(typeof x === \\"number\\"); + +// Feature check: +function foo(x: string | Array): string { + if (is_string(x)) { + // The use of \`is_string\` as a conditional check + // should guarantee the narrowing of the type of \`x\` + // to string. + return x; + } else { + // Accordingly the negation of the above check + // guarantees that \`x\` here is an Array + return x.join(); + } +} +" +`; + exports[`logical-or.js 1`] = ` "// @flow @@ -218,6 +385,57 @@ Number(dollars) || 0; " `; +exports[`logical-or.js 2`] = ` +"// @flow + +// Sanity check: +// - conditional functions do not affect behavior of conditional +// expressions (e.g. \`||\`) + +declare function r(x: string): number; +var s = 'a'; +var n = r(s) || 1; +(n: number); + +var x = \\"\\"; +if (x = r(s) || 1) { + (x: number); +} + +declare var dollars: mixed; + +function foo(x: mixed) { return 1; } +(foo(dollars) || 0); + +(Number(dollars) || 0); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Sanity check: +// - conditional functions do not affect behavior of conditional +// expressions (e.g. \`||\`) + +declare function r(x: string): number; +var s = \\"a\\"; +var n = r(s) || 1; +(n: number); + +var x = \\"\\"; +if ((x = r(s) || 1)) { + (x: number); +} + +declare var dollars: mixed; + +function foo(x: mixed) { + return 1; +} +foo(dollars) || 0; + +Number(dollars) || 0; +" +`; + exports[`object-invariant.js 1`] = ` "// @flow @@ -270,6 +488,58 @@ function f(_this: { m: ?Meeting }): string { " `; +exports[`object-invariant.js 2`] = ` +"// @flow + +// Sanity check: +// - preserving \`havoc\` semantics + +type Meeting = { + organizer: ?Invitee, + es: Array +} + +type Invitee = { + fbid: number +} + +function f(_this: { m: ?Meeting }): string { + if (!_this.m) { + return \\"0\\"; + } + + if (_this.m.es.some((a) => a.fbid === 0)) { + + } + return \\"3\\"; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Sanity check: +// - preserving \`havoc\` semantics + +type Meeting = { + organizer: ?Invitee, + es: Array +}; + +type Invitee = { + fbid: number +}; + +function f(_this: { m: ?Meeting }): string { + if (!_this.m) { + return \\"0\\"; + } + + if (_this.m.es.some(a => a.fbid === 0)) { + } + return \\"3\\"; +} +" +`; + exports[`orig-string-tag-check.js 1`] = ` "// @flow @@ -298,6 +568,34 @@ function foo(x: string | Array): string { " `; +exports[`orig-string-tag-check.js 2`] = ` +"// @flow + +// The original first-order case + +function foo(x: string | Array): string { + if (typeof x === \\"string\\") { + return x; // [ERROR] x: Array doesn't match return type + } + else { + return x.join(); // [ERROR] x: string doesn't have .join method + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// The original first-order case + +function foo(x: string | Array): string { + if (typeof x === \\"string\\") { + return x; // [ERROR] x: Array doesn't match return type + } else { + return x.join(); // [ERROR] x: string doesn't have .join method + } +} +" +`; + exports[`sanity-fall-through.js 1`] = ` "// @flow @@ -329,6 +627,37 @@ function foo(s: Array): string { " `; +exports[`sanity-fall-through.js 2`] = ` +"// @flow + +// Sanity check: +// - we should still be getting an error at the second return statement + +declare function pred(x: T): boolean; + +function foo(s: Array): string { + if (pred(s)) { + return \\"1\\"; + } + return 1; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Sanity check: +// - we should still be getting an error at the second return statement + +declare function pred(x: T): boolean; + +function foo(s: Array): string { + if (pred(s)) { + return \\"1\\"; + } + return 1; +} +" +`; + exports[`sanity-invalid-calls.js 1`] = ` "// @flow @@ -371,6 +700,48 @@ function foo(s: Array): string { " `; +exports[`sanity-invalid-calls.js 2`] = ` +"// @flow + +// Sanity check: +// - invalid calls at predicate positions + +declare function pred(x: T): boolean; + +function foo(s: Array): string { + + if ((1)(s)) { + return \\"1\\"; + } + + if ((pred + 1)(\\"s\\")) { + return \\"1\\"; + } + + return \\"1\\" +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Sanity check: +// - invalid calls at predicate positions + +declare function pred(x: T): boolean; + +function foo(s: Array): string { + if (1(s)) { + return \\"1\\"; + } + + if ((pred + 1)(\\"s\\")) { + return \\"1\\"; + } + + return \\"1\\"; +} +" +`; + exports[`sanity-is-string-bug.js 1`] = ` "// @flow @@ -406,6 +777,41 @@ function bar(x: string | Array): string { " `; +exports[`sanity-is-string-bug.js 2`] = ` +"// @flow + +declare function is_string(x: mixed): boolean %checks(typeof x === \\"string\\"); +declare function is_number(x: mixed): boolean %checks(typeof x === \\"number\\"); + +// Sanity check: +// - Erroneous logic + +function bar(x: string | Array): string { + if (is_number(x)) { + return x; + } else { + return x.join(); // error: both string and Array can flow to x + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +declare function is_string(x: mixed): boolean %checks(typeof x === \\"string\\"); +declare function is_number(x: mixed): boolean %checks(typeof x === \\"number\\"); + +// Sanity check: +// - Erroneous logic + +function bar(x: string | Array): string { + if (is_number(x)) { + return x; + } else { + return x.join(); // error: both string and Array can flow to x + } +} +" +`; + exports[`sanity-parameter-mismatch.js 1`] = ` "// @flow @@ -431,6 +837,31 @@ foo(3, 3); " `; +exports[`sanity-parameter-mismatch.js 2`] = ` +"// @flow + +// Sanity check: make sure the parameters are checked as usual + +declare function foo( + input: mixed, + types: string | Array +): boolean %checks(typeof input === \\"string\\" || Array.isArray(input)); + +foo(3, 3); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Sanity check: make sure the parameters are checked as usual + +declare function foo( + input: mixed, + types: string | Array +): boolean %checks(typeof input === \\"string\\" || Array.isArray(input)); + +foo(3, 3); +" +`; + exports[`sanity-pred-with-body.js 1`] = ` "// @flow @@ -467,6 +898,42 @@ function foo(x: string | Array): string { " `; +exports[`sanity-pred-with-body.js 2`] = ` +"// @flow + +// Sanity check: +// - predicate functions cannot have bodies (can only be declarations) + +function pred(x: mixed): boolean %checks(typeof x === \\"string\\") { // error: cannot use pred type here + return typeof x === \\"string\\"; +} + +function foo(x: string | Array): string { + if (pred(x)) { + return x; + } + return \\"1\\" +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Sanity check: +// - predicate functions cannot have bodies (can only be declarations) + +function pred(x: mixed): boolean %checks(typeof x === \\"string\\") { + // error: cannot use pred type here + return typeof x === \\"string\\"; +} + +function foo(x: string | Array): string { + if (pred(x)) { + return x; + } + return \\"1\\"; +} +" +`; + exports[`sanity-return-type.js 1`] = ` "// @flow @@ -477,3 +944,14 @@ declare function f2(x: mixed): string %checks(Array.isArray(x)); declare function f2(x: mixed): string %checks(Array.isArray(x)); " `; + +exports[`sanity-return-type.js 2`] = ` +"// @flow + +declare function f2(x: mixed): string %checks(Array.isArray(x)); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +declare function f2(x: mixed): string %checks(Array.isArray(x)); +" +`; diff --git a/tests/flow/predicates-declared/jsfmt.spec.js b/tests/flow/predicates-declared/jsfmt.spec.js index 989047bc..cd610266 100644 --- a/tests/flow/predicates-declared/jsfmt.spec.js +++ b/tests/flow/predicates-declared/jsfmt.spec.js @@ -1 +1,2 @@ run_spec(__dirname); +run_spec(__dirname, { parser: 'babylon' }); diff --git a/tests/flow/predicates-inferred/__snapshots__/jsfmt.spec.js.snap b/tests/flow/predicates-inferred/__snapshots__/jsfmt.spec.js.snap index 1bc595ec..1e35d7e7 100644 --- a/tests/flow/predicates-inferred/__snapshots__/jsfmt.spec.js.snap +++ b/tests/flow/predicates-inferred/__snapshots__/jsfmt.spec.js.snap @@ -59,6 +59,65 @@ function bak(z: string | number): number { " `; +exports[`sanity.js 2`] = ` +"// @flow + +// Sanity check: shouldn't be allowed to declare a predicate AND use \`chekcs\` + +function check(y): %checks(typeof y === \\"string\\") { + return typeof y === \\"number\\"; +} + +declare var y: number | boolean; + +if (check(y)) { + (y: number); +} + +// Sanity: disallowed body +function indirect_is_number(y): %checks { + var y = 1; + return typeof y === \\"number\\"; +} + +function bak(z: string | number): number { + if (indirect_is_number(z)) { + return z; + } else { + return z.length; + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Sanity check: shouldn't be allowed to declare a predicate AND use \`chekcs\` + +function check(y): %checks(typeof y === \\"string\\") { + return typeof y === \\"number\\"; +} + +declare var y: number | boolean; + +if (check(y)) { + (y: number); +} + +// Sanity: disallowed body +function indirect_is_number(y): %checks { + var y = 1; + return typeof y === \\"number\\"; +} + +function bak(z: string | number): number { + if (indirect_is_number(z)) { + return z; + } else { + return z.length; + } +} +" +`; + exports[`sanity-multi-params.js 1`] = ` "// @flow @@ -92,6 +151,39 @@ function foo(x: string | Array): string { " `; +exports[`sanity-multi-params.js 2`] = ` +"// @flow + +// Feature: multi params +function multi_param(w,x,y,z): %checks { + return typeof z === \\"string\\"; +} + +function foo(x: string | Array): string { + if (multi_param(\\"1\\", \\"2\\", x, \\"3\\")) { + return x; + } else { + return x.join(); + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Feature: multi params +function multi_param(w, x, y, z): %checks { + return typeof z === \\"string\\"; +} + +function foo(x: string | Array): string { + if (multi_param(\\"1\\", \\"2\\", x, \\"3\\")) { + return x; + } else { + return x.join(); + } +} +" +`; + exports[`sanity-ordering.js 1`] = ` "// @flow @@ -133,6 +225,47 @@ function dotAccess(head, create) { " `; +exports[`sanity-ordering.js 2`] = ` +"// @flow + +declare var key: string; +declare var obj: { page: ?Object; }; + +if (dotAccess(obj)) { + (obj.page: Object); +} + +function dotAccess(head, create) { + const path = 'path.location'; + const stack = path.split('.'); + do { + const key = stack.shift(); + head = head[key] || create && (head[key] = {}); + } while (stack.length && head); + return head; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +declare var key: string; +declare var obj: { page: ?Object }; + +if (dotAccess(obj)) { + (obj.page: Object); +} + +function dotAccess(head, create) { + const path = \\"path.location\\"; + const stack = path.split(\\".\\"); + do { + const key = stack.shift(); + head = head[key] || (create && (head[key] = {})); + } while (stack.length && head); + return head; +} +" +`; + exports[`sanity-unbound-var.js 1`] = ` "// @flow @@ -172,6 +305,45 @@ function foo(x: string | Array): string { " `; +exports[`sanity-unbound-var.js 2`] = ` +"// @flow + +declare var y: mixed; + +// Sanity check: this should fail, because the preficate function +// checks \`y\` instead of \`x\`. +function err(x): %checks { + return typeof y === \\"string\\"; +} + +function foo(x: string | Array): string { + if (err(x)) { + return x; + } else { + return x.join(); + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +declare var y: mixed; + +// Sanity check: this should fail, because the preficate function +// checks \`y\` instead of \`x\`. +function err(x): %checks { + return typeof y === \\"string\\"; +} + +function foo(x: string | Array): string { + if (err(x)) { + return x; + } else { + return x.join(); + } +} +" +`; + exports[`simple-predicate-func.js 1`] = ` "// @flow @@ -323,6 +495,157 @@ declare function from_two_strings(x: string, y: string): void; " `; +exports[`simple-predicate-func.js 2`] = ` +"// @flow + +function is_string(y): %checks { + return typeof y === \\"string\\"; +} + +function is_bool(y): %checks { + return typeof y === \\"boolean\\"; +} + +function is_number(y): %checks { + return typeof y === \\"number\\"; +} + +// Feature check: +function foo(x: string | Array): string { + if (is_string(x)) { + // The use of \`is_string\` as a conditional check + // should guarantee the narrowing of the type of \`x\` + // to string. + return x; + } else { + // Accordingly the negation of the above check + // guarantees that \`x\` here is an Array + return x.join(); + } +} + +// Same as above but refining an offset +function bar(z: { f: string | Array}): string { + if (is_string(z.f)) { + return z.f; + } else { + return z.f.join(); + } +} + +function is_number_or_bool(y): %checks { + return is_number(y) || is_bool(y); +} + +function baz(z: string | number): number { + if (is_number_or_bool(z)) { + return z; + } else { + return z.length; + } +} + +// Feature: multi params +function multi_param(w,x,y,z): %checks { + return typeof z === \\"string\\"; +} + +function foo(x: string | Array): string { + if (multi_param(\\"1\\", \\"2\\", \\"3\\", x)) { + return x; + } else { + return x.join(); + } +} + +function foo(a, b) { + if (two_strings(a, b)) { + from_two_strings(a, b); + } +} + +function two_strings(x,y): %checks { + return is_string(x) && is_string(y) ; +} + +declare function from_two_strings(x: string, y: string): void; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +function is_string(y): %checks { + return typeof y === \\"string\\"; +} + +function is_bool(y): %checks { + return typeof y === \\"boolean\\"; +} + +function is_number(y): %checks { + return typeof y === \\"number\\"; +} + +// Feature check: +function foo(x: string | Array): string { + if (is_string(x)) { + // The use of \`is_string\` as a conditional check + // should guarantee the narrowing of the type of \`x\` + // to string. + return x; + } else { + // Accordingly the negation of the above check + // guarantees that \`x\` here is an Array + return x.join(); + } +} + +// Same as above but refining an offset +function bar(z: { f: string | Array }): string { + if (is_string(z.f)) { + return z.f; + } else { + return z.f.join(); + } +} + +function is_number_or_bool(y): %checks { + return is_number(y) || is_bool(y); +} + +function baz(z: string | number): number { + if (is_number_or_bool(z)) { + return z; + } else { + return z.length; + } +} + +// Feature: multi params +function multi_param(w, x, y, z): %checks { + return typeof z === \\"string\\"; +} + +function foo(x: string | Array): string { + if (multi_param(\\"1\\", \\"2\\", \\"3\\", x)) { + return x; + } else { + return x.join(); + } +} + +function foo(a, b) { + if (two_strings(a, b)) { + from_two_strings(a, b); + } +} + +function two_strings(x, y): %checks { + return is_string(x) && is_string(y); +} + +declare function from_two_strings(x: string, y: string): void; +" +`; + exports[`simple-predicate-func-post.js 1`] = ` "// @flow @@ -369,3 +692,50 @@ function is_string(x): %checks { } " `; + +exports[`simple-predicate-func-post.js 2`] = ` +"// @flow + +// Feature check: +// The predicate function is defined after the conditional check + +function foo(x: string | Array): string { + if (is_string(x)) { + // The use of \`is_string\` as a conditional check + // should guarantee the narrowing of the type of \`x\` + // to string. + return x; + } else { + // Accordingly the negation of the above check + // guarantees that \`x\` here is an Array + return x.join(); + } +} + +function is_string(x): %checks { + return typeof x === \\"string\\"; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Feature check: +// The predicate function is defined after the conditional check + +function foo(x: string | Array): string { + if (is_string(x)) { + // The use of \`is_string\` as a conditional check + // should guarantee the narrowing of the type of \`x\` + // to string. + return x; + } else { + // Accordingly the negation of the above check + // guarantees that \`x\` here is an Array + return x.join(); + } +} + +function is_string(x): %checks { + return typeof x === \\"string\\"; +} +" +`; diff --git a/tests/flow/predicates-inferred/jsfmt.spec.js b/tests/flow/predicates-inferred/jsfmt.spec.js index 989047bc..5399f5ff 100644 --- a/tests/flow/predicates-inferred/jsfmt.spec.js +++ b/tests/flow/predicates-inferred/jsfmt.spec.js @@ -1 +1,2 @@ run_spec(__dirname); +run_spec(__dirname, { parser: "babylon" }); diff --git a/tests/flow/predicates-parsing/__snapshots__/jsfmt.spec.js.snap b/tests/flow/predicates-parsing/__snapshots__/jsfmt.spec.js.snap index c8c934b2..8b2077be 100644 --- a/tests/flow/predicates-parsing/__snapshots__/jsfmt.spec.js.snap +++ b/tests/flow/predicates-parsing/__snapshots__/jsfmt.spec.js.snap @@ -15,6 +15,21 @@ declare function f2(x: mixed): boolean %checks; " `; +exports[`fail-0.js 2`] = ` +"// @flow + +// Error: 'declare', 'checks' but missing predicate + +declare function f2(x: mixed): boolean %checks; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Error: 'declare', 'checks' but missing predicate + +declare function f2(x: mixed): boolean %checks; +" +`; + exports[`fail-1.js 1`] = ` "// @flow @@ -30,6 +45,21 @@ function f6(x: mixed): %checks(x !== null) {} " `; +exports[`fail-1.js 2`] = ` +"// @flow + +// Error: no return statement + +function f6(x: mixed): %checks (x !== null) { } +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Error: no return statement + +function f6(x: mixed): %checks(x !== null) {} +" +`; + exports[`fail-2.js 1`] = ` "// @flow @@ -47,6 +77,23 @@ var a2 = (x: mixed): %checks(x !== null) => { " `; +exports[`fail-2.js 2`] = ` +"// @flow + +var a2 = (x: mixed): %checks (x !== null) => { // Error: body form + var x = 1; return x; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +var a2 = (x: mixed): %checks(x !== null) => { + // Error: body form + var x = 1; + return x; +}; +" +`; + exports[`fail-3.js 1`] = ` "// @flow @@ -68,6 +115,27 @@ var a2 = (x: mixed): %checks(x !== null) => x !== null; " `; +exports[`fail-3.js 2`] = ` +"// @flow + +// Cannot declare predicate with a function body is present. + +function f5(x: mixed): %checks (x !== null) { return x !== null } + +var a2 = (x: mixed): %checks (x !== null) => x !== null; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +// Cannot declare predicate with a function body is present. + +function f5(x: mixed): %checks(x !== null) { + return x !== null; +} + +var a2 = (x: mixed): %checks(x !== null) => x !== null; +" +`; + exports[`pass.js 1`] = ` "// @flow @@ -128,3 +196,64 @@ declare function f(x: mixed): checks; typeof x === null; " `; + +exports[`pass.js 2`] = ` +"// @flow + +declare function f1(x: mixed): boolean; + +declare function f3(x: mixed): boolean %checks (x !== null); + +declare function f4(x: mixed): boolean %checks (x !== null); + +function f7(x: mixed): %checks { return x !== null } + +var a0 = (x: mixed) => x !== null; + +var a1 = (x: mixed): %checks => x !== null; + +(x): %checks => x !== null; + +const insert_a_really_big_predicated_arrow_function_name_here = (x) + : %checks => x !== null; + +declare var x; +(x) +checks => 123; + +type checks = any; + +declare function f(x: mixed): checks +(typeof x === null); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// @flow + +declare function f1(x: mixed): boolean; + +declare function f3(x: mixed): boolean %checks(x !== null); + +declare function f4(x: mixed): boolean %checks(x !== null); + +function f7(x: mixed): %checks { + return x !== null; +} + +var a0 = (x: mixed) => x !== null; + +var a1 = (x: mixed): %checks => x !== null; + +(x): %checks => x !== null; + +const insert_a_really_big_predicated_arrow_function_name_here = (x): %checks => + x !== null; + +declare var x; +x; +checks => 123; + +type checks = any; + +declare function f(x: mixed): checks; +typeof x === null; +" +`; diff --git a/tests/flow/predicates-parsing/jsfmt.spec.js b/tests/flow/predicates-parsing/jsfmt.spec.js index 989047bc..5399f5ff 100644 --- a/tests/flow/predicates-parsing/jsfmt.spec.js +++ b/tests/flow/predicates-parsing/jsfmt.spec.js @@ -1 +1,2 @@ run_spec(__dirname); +run_spec(__dirname, { parser: "babylon" }); diff --git a/yarn.lock b/yarn.lock index e942743a..0681630f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -274,14 +274,14 @@ babel-types@^6.16.0, babel-types@^6.18.0, babel-types@^6.21.0: lodash "^4.2.0" to-fast-properties "^1.0.1" +babylon@7.0.0-beta.8: + version "7.0.0-beta.8" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.8.tgz#2bdc5ae366041442c27e068cce6f0d7c06ea9949" + babylon@^6.11.0, babylon@^6.13.0: version "6.15.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e" -babylon@babylon@7.0.0-beta.4: - version "7.0.0-beta.4" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.4.tgz#82db799d2667f61bbaf34456dbfa91c37613459d" - balanced-match@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"