diff --git a/docs/options.md b/docs/options.md index 8a9cfee7..3f6ca94b 100644 --- a/docs/options.md +++ b/docs/options.md @@ -96,6 +96,19 @@ Put the `>` of a multi-line JSX element at the end of the last line instead of b | ------- | ------------------------- | ---------------------------- | | `false` | `--jsx-bracket-same-line` | `jsxBracketSameLine: ` | +## Arrow Function Parentheses + +Include parentheses around a sole arrow function parameter. + +Valid options: + +* `"avoid"` - Omit parens when possible. Example: `x => x` +* `"always"` - Always include parens. Example: `(x) => x` + +| Default | CLI Override | API Override | +| --------- | ----------------------------------------------- | ----------------------------------------------- | +| `"avoid"` | --arrow-parens | arrowParens: "" | + ## Range Format only a segment of a file. diff --git a/src/cli-constant.js b/src/cli-constant.js index 92ce7e37..01854069 100644 --- a/src/cli-constant.js +++ b/src/cli-constant.js @@ -77,6 +77,23 @@ const categoryOrder = [ * Note: The options below are sorted alphabetically. */ const detailedOptions = normalizeDetailedOptions({ + "arrow-parens": { + type: "choice", + category: CATEGORY_FORMAT, + forwardToApi: true, + description: "Include parentheses around a sole arrow function parameter.", + default: "avoid", + choices: [ + { + value: "avoid", + description: "Omit parens when possible. Example: `x => x`" + }, + { + value: "always", + description: "Always include parens. Example: `(x) => x`" + } + ] + }, "bracket-spacing": { type: "boolean", category: CATEGORY_FORMAT, diff --git a/src/options.js b/src/options.js index 19c34ab8..f0d96315 100644 --- a/src/options.js +++ b/src/options.js @@ -21,7 +21,8 @@ const defaults = { insertPragma: false, requirePragma: false, semi: true, - proseWrap: true + proseWrap: true, + arrowParens: "avoid" }; const exampleConfig = Object.assign({}, defaults, { diff --git a/src/printer.js b/src/printer.js index 4351710d..3ac29dfa 100644 --- a/src/printer.js +++ b/src/printer.js @@ -214,14 +214,6 @@ function genericPrint(path, options, printPath, args) { needsParens = path.needsParens(options); } - if (node.type) { - // HACK: ASI prevention in no-semi mode relies on knowledge of whether - // or not a paren has been inserted (see `exprNeedsASIProtection()`). - // For now, we're just passing that information by mutating the AST here, - // but it would be nice to find a cleaner way to do this. - node.needsParens = needsParens; - } - const parts = []; if (needsParens) { parts.unshift("("); @@ -501,7 +493,7 @@ function genericPrintNoParens(path, options, print, args) { parts.push("async "); } - if (canPrintParamsWithoutParens(n)) { + if (shouldPrintParamsWithoutParens(path, options)) { parts.push(path.call(print, "params", 0)); } else { parts.push( @@ -855,7 +847,6 @@ function genericPrintNoParens(path, options, print, args) { case "NewExpression": case "CallExpression": { const isNew = n.type === "NewExpression"; - const unitTestRe = /^(f|x)?(it|describe|test)$/; const optional = printOptionalToken(path); if ( @@ -869,22 +860,7 @@ function genericPrintNoParens(path, options, print, args) { isTemplateOnItsOwnLine(n.arguments[0], options.originalText)) || // Keep test declarations on a single line // e.g. `it('long name', () => {` - (!isNew && - ((n.callee.type === "Identifier" && unitTestRe.test(n.callee.name)) || - (n.callee.type === "MemberExpression" && - n.callee.object.type === "Identifier" && - n.callee.property.type === "Identifier" && - unitTestRe.test(n.callee.object.name) && - (n.callee.property.name === "only" || - n.callee.property.name === "skip"))) && - n.arguments.length === 2 && - (n.arguments[0].type === "StringLiteral" || - n.arguments[0].type === "TemplateLiteral" || - (n.arguments[0].type === "Literal" && - typeof n.arguments[0].value === "string")) && - (n.arguments[1].type === "FunctionExpression" || - n.arguments[1].type === "ArrowFunctionExpression") && - n.arguments[1].params.length <= 1) + (!isNew && isTestCall(n)) ) { return concat([ isNew ? "new " : "", @@ -2873,11 +2849,9 @@ function printStatementSequence(path, options, print) { !options.semi && !isClass && !isTheOnlyJSXElementInMarkdown(options, stmtPath) && - stmtNeedsASIProtection(stmtPath) + stmtNeedsASIProtection(stmtPath, options) ) { if (stmt.comments && stmt.comments.some(comment => comment.leading)) { - // Note: stmtNeedsASIProtection requires stmtPath to already be printed - // as it reads needsParens which is mutated on the instance parts.push(print(stmtPath, { needsSemi: true })); } else { parts.push(";", stmtPrinted); @@ -3228,6 +3202,11 @@ function printFunctionParams(path, print, options, expandArg, printTypeParams) { const parent = path.getParentNode(); + // don't break in specs, eg; `it("should maintain parens around done even when long", (done) => {})` + if (parent.type === "CallExpression" && isTestCall(parent)) { + return concat([typeParams, "(", join(", ", printed), ")"]); + } + const flowTypeAnnotations = [ "AnyTypeAnnotation", "NullLiteralTypeAnnotation", @@ -3282,6 +3261,20 @@ function printFunctionParams(path, print, options, expandArg, printTypeParams) { ]); } +function shouldPrintParamsWithoutParens(path, options) { + if (options.arrowParens === "always") { + return false; + } + + if (options.arrowParens === "avoid") { + const node = path.getValue(); + return canPrintParamsWithoutParens(node); + } + + // Fallback default; should be unreachable + return false; +} + function canPrintParamsWithoutParens(node) { return ( node.params.length === 1 && @@ -3527,7 +3520,15 @@ function printTypeParameters(path, options, print, paramsKey) { return path.call(print, paramsKey); } + const grandparent = path.getNode(2); + + const isParameterInTestCall = + grandparent != null && + grandparent.type === "CallExpression" && + isTestCall(grandparent); + const shouldInline = + isParameterInTestCall || n[paramsKey].length === 0 || (n[paramsKey].length === 1 && (shouldHugType(n[paramsKey][0]) || @@ -4683,15 +4684,43 @@ function getLeftSide(node) { ); } -function exprNeedsASIProtection(node) { - // HACK: node.needsParens is added in `genericPrint()` for the sole purpose - // of being used here. It'd be preferable to find a cleaner way to do this. +function getLeftSidePathName(path, node) { + if (node.expressions) { + return ["expressions", 0]; + } + if (node.left) { + return ["left"]; + } + if (node.test) { + return ["test"]; + } + if (node.callee) { + return ["callee"]; + } + if (node.object) { + return ["object"]; + } + if (node.tag) { + return ["tag"]; + } + if (node.argument) { + return ["argument"]; + } + if (node.expression) { + return ["expression"]; + } + throw new Error("Unexpected node has no left side", node); +} + +function exprNeedsASIProtection(path, options) { + const node = path.getValue(); + const maybeASIProblem = - node.needsParens || + path.needsParens(options) || node.type === "ParenthesizedExpression" || node.type === "TypeCastExpression" || (node.type === "ArrowFunctionExpression" && - !canPrintParamsWithoutParens(node)) || + !shouldPrintParamsWithoutParens(path, options)) || node.type === "ArrayExpression" || node.type === "ArrayPattern" || (node.type === "UnaryExpression" && @@ -4713,17 +4742,25 @@ function exprNeedsASIProtection(node) { return false; } - return exprNeedsASIProtection(getLeftSide(node)); + return path.call.apply( + path, + [childPath => exprNeedsASIProtection(childPath, options)].concat( + getLeftSidePathName(path, node) + ) + ); } -function stmtNeedsASIProtection(path) { +function stmtNeedsASIProtection(path, options) { const node = path.getNode(); if (node.type !== "ExpressionStatement") { return false; } - return exprNeedsASIProtection(node.expression); + return path.call( + childPath => exprNeedsASIProtection(childPath, options), + "expression" + ); } function classPropMayCauseASIProblems(path) { @@ -4992,6 +5029,28 @@ function isObjectType(n) { return n.type === "ObjectTypeAnnotation" || n.type === "TSTypeLiteral"; } +// eg; `describe("some string", (done) => {})` +function isTestCall(n) { + const unitTestRe = /^(f|x)?(it|describe|test)$/; + return ( + ((n.callee.type === "Identifier" && unitTestRe.test(n.callee.name)) || + (n.callee.type === "MemberExpression" && + n.callee.object.type === "Identifier" && + n.callee.property.type === "Identifier" && + unitTestRe.test(n.callee.object.name) && + (n.callee.property.name === "only" || + n.callee.property.name === "skip"))) && + n.arguments.length === 2 && + (n.arguments[0].type === "StringLiteral" || + n.arguments[0].type === "TemplateLiteral" || + (n.arguments[0].type === "Literal" && + typeof n.arguments[0].value === "string")) && + (n.arguments[1].type === "FunctionExpression" || + n.arguments[1].type === "ArrowFunctionExpression") && + n.arguments[1].params.length <= 1 + ); +} + function printAstToDoc(ast, options, addAlignmentSize) { addAlignmentSize = addAlignmentSize || 0; diff --git a/tests/arrow-call/__snapshots__/jsfmt.spec.js.snap b/tests/arrow-call/__snapshots__/jsfmt.spec.js.snap index 0597d87a..92c45557 100644 --- a/tests/arrow-call/__snapshots__/jsfmt.spec.js.snap +++ b/tests/arrow-call/__snapshots__/jsfmt.spec.js.snap @@ -189,3 +189,98 @@ const composition = (ViewComponent, ContainerComponent) => }; `; + +exports[`arrow_call.js 3`] = ` +const testResults = results.testResults.map(testResult => + formatResult(testResult, formatter, reporter) +); + +it('mocks regexp instances', () => { + expect( + () => moduleMocker.generateFromMetadata(moduleMocker.getMetadata(/a/)), + ).not.toThrow(); +}); + +expect(() => asyncRequest({ url: "/test-endpoint" })) + .toThrowError(/Required parameter/); + +expect(() => asyncRequest({ url: "/test-endpoint-but-with-a-long-url" })) + .toThrowError(/Required parameter/); + +expect(() => asyncRequest({ url: "/test-endpoint-but-with-a-suuuuuuuuper-long-url" })) + .toThrowError(/Required parameter/); + +expect(() => asyncRequest({ type: "foo", url: "/test-endpoint" })) + .not.toThrowError(); + +expect(() => asyncRequest({ type: "foo", url: "/test-endpoint-but-with-a-long-url" })) + .not.toThrowError(); + +const a = Observable + .fromPromise(axiosInstance.post('/carts/mine')) + .map((response) => response.data) + +const b = Observable.fromPromise(axiosInstance.get(url)) + .map((response) => response.data) + +func( + veryLoooooooooooooooooooooooongName, + veryLooooooooooooooooooooooooongName => + veryLoooooooooooooooongName.something() +); + +const composition = (ViewComponent, ContainerComponent) => + class extends React.Component { + static propTypes = {}; + }; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const testResults = results.testResults.map((testResult) => + formatResult(testResult, formatter, reporter) +); + +it("mocks regexp instances", () => { + expect(() => + moduleMocker.generateFromMetadata(moduleMocker.getMetadata(/a/)) + ).not.toThrow(); +}); + +expect(() => asyncRequest({ url: "/test-endpoint" })).toThrowError( + /Required parameter/ +); + +expect(() => + asyncRequest({ url: "/test-endpoint-but-with-a-long-url" }) +).toThrowError(/Required parameter/); + +expect(() => + asyncRequest({ url: "/test-endpoint-but-with-a-suuuuuuuuper-long-url" }) +).toThrowError(/Required parameter/); + +expect(() => + asyncRequest({ type: "foo", url: "/test-endpoint" }) +).not.toThrowError(); + +expect(() => + asyncRequest({ type: "foo", url: "/test-endpoint-but-with-a-long-url" }) +).not.toThrowError(); + +const a = Observable.fromPromise(axiosInstance.post("/carts/mine")).map( + (response) => response.data +); + +const b = Observable.fromPromise(axiosInstance.get(url)).map( + (response) => response.data +); + +func( + veryLoooooooooooooooooooooooongName, + (veryLooooooooooooooooooooooooongName) => + veryLoooooooooooooooongName.something() +); + +const composition = (ViewComponent, ContainerComponent) => + class extends React.Component { + static propTypes = {}; + }; + +`; diff --git a/tests/arrow-call/jsfmt.spec.js b/tests/arrow-call/jsfmt.spec.js index 4e02696c..d2d553f6 100644 --- a/tests/arrow-call/jsfmt.spec.js +++ b/tests/arrow-call/jsfmt.spec.js @@ -1,2 +1,3 @@ run_spec(__dirname, null, ["typescript"]); run_spec(__dirname, { trailingComma: "all" }, ["typescript"]); +run_spec(__dirname, { arrowParens: "always" }, ["typescript"]); diff --git a/tests/arrows/__snapshots__/jsfmt.spec.js.snap b/tests/arrows/__snapshots__/jsfmt.spec.js.snap index 065c3827..e9786867 100644 --- a/tests/arrows/__snapshots__/jsfmt.spec.js.snap +++ b/tests/arrows/__snapshots__/jsfmt.spec.js.snap @@ -32,6 +32,10 @@ x => (y = z); x => (y += z); f(a => ({})) + 1; (a => ({})) || 0; +a = b => c; +a = b => { + return c +}; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (a => {}).length; typeof (() => {}); @@ -65,6 +69,86 @@ x => (y = z); x => (y += z); f(a => ({})) + 1; (a => ({})) || 0; +a = b => c; +a = b => { + return c; +}; + +`; + +exports[`arrow_function_expression.js 2`] = ` +(a => {}).length +typeof (() => {}); +export default (() => {})(); +(() => {})()\`\`; +(() => {})\`\`; +new (() => {}); +if ((() => {}) ? 1 : 0) {} +let f = () => ({}()) +let a = () => ({} instanceof a); +a = () => ({} && a); +a = () => ({}() && a); +a = () => ({} && a && b); +a = () => ({} + a); +a = () => ({}()() && a); +a = () => ({}.b && a); +a = () => ({}[b] && a); +a = () => ({}\`\` && a); +a = () => ({} = 0); +a = () => ({}, a); +a => a instanceof {}; +a => ({}().b && 0) +a => ({}().c = 0) +x => ({}()()) +x => ({}()\`\`) +x => ({}().b); +a = b => c; +a = (b?) => c; +x => (y = z); +x => (y += z); +f(a => ({})) + 1; +(a => ({})) || 0; +a = b => c; +a = b => { + return c +}; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +((a) => {}).length; +typeof (() => {}); +export default (() => {})(); +(() => {})()\`\`; +(() => {})\`\`; +new (() => {})(); +if ((() => {}) ? 1 : 0) { +} +let f = () => ({}()); +let a = () => ({} instanceof a); +a = () => ({} && a); +a = () => ({}() && a); +a = () => ({} && a && b); +a = () => ({} + a); +a = () => ({}()() && a); +a = () => ({}.b && a); +a = () => ({}[b] && a); +a = () => ({}\`\` && a); +a = () => ({} = 0); +a = () => ({}, a); +(a) => a instanceof {}; +(a) => ({}().b && 0); +(a) => ({}().c = 0); +(x) => ({}()()); +(x) => ({}()\`\`); +(x) => ({}().b); +a = (b) => c; +a = (b?) => c; +(x) => (y = z); +(x) => (y += z); +f((a) => ({})) + 1; +((a) => ({})) || 0; +a = (b) => c; +a = (b) => { + return c; +}; `; @@ -75,6 +159,13 @@ a = () => ({} = this); `; +exports[`block_like.js 2`] = ` +a = () => ({} = this); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +a = () => ({} = this); + +`; + exports[`call.js 1`] = ` Seq(typeDef.interface.groups).forEach(group => Seq(group.members).forEach((member, memberName) => @@ -248,6 +339,179 @@ fooooooooooooooooooooooooooooooooooooooooooooooooooo(action => next => `; +exports[`call.js 2`] = ` +Seq(typeDef.interface.groups).forEach(group => + Seq(group.members).forEach((member, memberName) => + markdownDoc( + member.doc, + { typePath: typePath.concat(memberName.slice(1)), + signatures: member.signatures } + ) + ) +) + +const promiseFromCallback = fn => + new Promise((resolve, reject) => + fn((err, result) => { + if (err) return reject(err); + return resolve(result); + }) + ); + +runtimeAgent.getProperties( + objectId, + false, // ownProperties + false, // accessorPropertiesOnly + false, // generatePreview + (error, properties, internalProperties) => { + return 1 + }, +); + +function render() { + return ( + + this.setState({progress: Math.round(100 * e.nativeEvent.loaded / e.nativeEvent.total)})} + /> + + ); +} + +function render() { + return ( + + + this.setState({ + progress: Math.round( + 100 * e.nativeEvent.loaded / e.nativeEvent.total, + ), + })} + /> + + ); +} + +function render() { + return ( + + + this.setState({ + progress: Math.round( + 100 * e.nativeEvent.loaded / e.nativeEvent.total, + ), + })} + /> + + ); +} + +jest.mock( + '../SearchSource', + () => class { + findMatchingTests(pattern) { + return {paths: []}; + } + }, +); + +fooooooooooooooooooooooooooooooooooooooooooooooooooo(action => next => + dispatch(action), +); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Seq(typeDef.interface.groups).forEach((group) => + Seq(group.members).forEach((member, memberName) => + markdownDoc(member.doc, { + typePath: typePath.concat(memberName.slice(1)), + signatures: member.signatures + }) + ) +); + +const promiseFromCallback = (fn) => + new Promise((resolve, reject) => + fn((err, result) => { + if (err) return reject(err); + return resolve(result); + }) + ); + +runtimeAgent.getProperties( + objectId, + false, // ownProperties + false, // accessorPropertiesOnly + false, // generatePreview + (error, properties, internalProperties) => { + return 1; + } +); + +function render() { + return ( + + + this.setState({ + progress: Math.round( + 100 * e.nativeEvent.loaded / e.nativeEvent.total + ) + }) + } + /> + + ); +} + +function render() { + return ( + + + this.setState({ + progress: Math.round( + 100 * e.nativeEvent.loaded / e.nativeEvent.total + ) + }) + } + /> + + ); +} + +function render() { + return ( + + + this.setState({ + progress: Math.round( + 100 * e.nativeEvent.loaded / e.nativeEvent.total + ) + }) + } + /> + + ); +} + +jest.mock( + "../SearchSource", + () => + class { + findMatchingTests(pattern) { + return { paths: [] }; + } + } +); + +fooooooooooooooooooooooooooooooooooooooooooooooooooo((action) => (next) => + dispatch(action) +); + +`; + exports[`comment.js 1`] = ` /** * Curried function that ends with a BEM CSS Selector @@ -313,11 +577,82 @@ export const bem = block => `; +exports[`comment.js 2`] = ` +/** + * Curried function that ends with a BEM CSS Selector + * + * @param {String} block - the BEM Block you'd like to select. + * @returns {Function} + */ +export const bem = block => + /** + * @param {String} [element] - the BEM Element within that block; if undefined, selects the block itself. + * @returns {Function} + */ + element => + /** + * @param {?String} [modifier] - the BEM Modifier for the Block or Element; if undefined, selects the Block or Element unmodified. + * @returns {String} + */ + modifier => + [ + ".", + css(block), + element ? \`__\${css(element)}\` : "", + modifier ? \`--\${css(modifier)}\` : "" + ].join(""); + + {info.item.widget.missingProp}} + data={data} +/> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/** + * Curried function that ends with a BEM CSS Selector + * + * @param {String} block - the BEM Block you'd like to select. + * @returns {Function} + */ +export const bem = (block) => + /** + * @param {String} [element] - the BEM Element within that block; if undefined, selects the block itself. + * @returns {Function} + */ + (element) => + /** + * @param {?String} [modifier] - the BEM Modifier for the Block or Element; if undefined, selects the Block or Element unmodified. + * @returns {String} + */ + (modifier) => + [ + ".", + css(block), + element ? \`__\${css(element)}\` : "", + modifier ? \`--\${css(modifier)}\` : "" + ].join(""); + + {info.item.widget.missingProp}} + data={data} +/>; + +`; + exports[`currying.js 1`] = ` const fn = b => c => d => { return 3; }; +const foo = (a, b) => c => d => { + return 3; +}; + +const bar = a => b => c => a + b + c + const mw = store => next => action => { return next(action) } @@ -330,6 +665,12 @@ const fn = b => c => d => { return 3; }; +const foo = (a, b) => c => d => { + return 3; +}; + +const bar = a => b => c => a + b + c; + const mw = store => next => action => { return next(action); }; @@ -340,6 +681,45 @@ const middleware = options => (req, res, next) => { `; +exports[`currying.js 2`] = ` +const fn = b => c => d => { + return 3; +}; + +const foo = (a, b) => c => d => { + return 3; +}; + +const bar = a => b => c => a + b + c + +const mw = store => next => action => { + return next(action) +} + +const middleware = options => (req, res, next) => { + // ... +}; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const fn = (b) => (c) => (d) => { + return 3; +}; + +const foo = (a, b) => (c) => (d) => { + return 3; +}; + +const bar = (a) => (b) => (c) => a + b + c; + +const mw = (store) => (next) => (action) => { + return next(action); +}; + +const middleware = (options) => (req, res, next) => { + // ... +}; + +`; + exports[`long-call-no-args.js 1`] = ` veryLongCall(VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_LONG_CONSTANT, () => {}) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -350,6 +730,16 @@ veryLongCall( `; +exports[`long-call-no-args.js 2`] = ` +veryLongCall(VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_LONG_CONSTANT, () => {}) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +veryLongCall( + VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_VERY_LONG_CONSTANT, + () => {} +); + +`; + exports[`long-contents.js 1`] = ` const foo = () => { expect(arg1, arg2, arg3).toEqual({message: 'test', messageType: 'SMS', status: 'Unknown', created: '11/01/2017 13:36'}); @@ -366,6 +756,102 @@ const foo = () => { `; +exports[`long-contents.js 2`] = ` +const foo = () => { + expect(arg1, arg2, arg3).toEqual({message: 'test', messageType: 'SMS', status: 'Unknown', created: '11/01/2017 13:36'}); +}; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const foo = () => { + expect(arg1, arg2, arg3).toEqual({ + message: "test", + messageType: "SMS", + status: "Unknown", + created: "11/01/2017 13:36" + }); +}; + +`; + +exports[`parens.js 1`] = ` +promise.then( + (result) => result, + (err) => err +) + +promise.then( + (result) => { f(); return result }, + (err) => { f(); return err } +) + +foo(a => b) +foo(a => { return b }) +foo(c, a => b) +foo(c, a => b, d) +foo(a => b, d) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +promise.then(result => result, err => err); + +promise.then( + result => { + f(); + return result; + }, + err => { + f(); + return err; + } +); + +foo(a => b); +foo(a => { + return b; +}); +foo(c, a => b); +foo(c, a => b, d); +foo(a => b, d); + +`; + +exports[`parens.js 2`] = ` +promise.then( + (result) => result, + (err) => err +) + +promise.then( + (result) => { f(); return result }, + (err) => { f(); return err } +) + +foo(a => b) +foo(a => { return b }) +foo(c, a => b) +foo(c, a => b, d) +foo(a => b, d) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +promise.then((result) => result, (err) => err); + +promise.then( + (result) => { + f(); + return result; + }, + (err) => { + f(); + return err; + } +); + +foo((a) => b); +foo((a) => { + return b; +}); +foo(c, (a) => b); +foo(c, (a) => b, d); +foo((a) => b, d); + +`; + exports[`short_body.js 1`] = ` const initializeSnapshotState = ( testFile: Path, @@ -383,9 +869,33 @@ const initializeSnapshotState = ( `; +exports[`short_body.js 2`] = ` +const initializeSnapshotState = ( + testFile: Path, + update: boolean, + testPath: string, + expand: boolean, +) => new SnapshotState(testFile, update, testPath, expand); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const initializeSnapshotState = ( + testFile: Path, + update: boolean, + testPath: string, + expand: boolean +) => new SnapshotState(testFile, update, testPath, expand); + +`; + exports[`type_params.js 1`] = ` (a) => { } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (a) => {}; `; + +exports[`type_params.js 2`] = ` +(a) => { } +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +(a) => {}; + +`; diff --git a/tests/arrows/arrow_function_expression.js b/tests/arrows/arrow_function_expression.js index 4e0071e0..ceae6d7a 100644 --- a/tests/arrows/arrow_function_expression.js +++ b/tests/arrows/arrow_function_expression.js @@ -29,3 +29,7 @@ x => (y = z); x => (y += z); f(a => ({})) + 1; (a => ({})) || 0; +a = b => c; +a = b => { + return c +}; diff --git a/tests/arrows/currying.js b/tests/arrows/currying.js index e1a4c7e2..1e3b36d1 100644 --- a/tests/arrows/currying.js +++ b/tests/arrows/currying.js @@ -2,6 +2,12 @@ const fn = b => c => d => { return 3; }; +const foo = (a, b) => c => d => { + return 3; +}; + +const bar = a => b => c => a + b + c + const mw = store => next => action => { return next(action) } diff --git a/tests/arrows/jsfmt.spec.js b/tests/arrows/jsfmt.spec.js index 6a6711ae..460cdeae 100644 --- a/tests/arrows/jsfmt.spec.js +++ b/tests/arrows/jsfmt.spec.js @@ -1 +1,6 @@ -run_spec(__dirname, { parser: "babylon" }, ["typescript"]); +run_spec(__dirname, { parser: "babylon", arrowParens: "avoid" }, [ + "typescript" +]); +run_spec(__dirname, { parser: "babylon", arrowParens: "always" }, [ + "typescript" +]); diff --git a/tests/arrows/parens.js b/tests/arrows/parens.js new file mode 100644 index 00000000..e9b5f424 --- /dev/null +++ b/tests/arrows/parens.js @@ -0,0 +1,15 @@ +promise.then( + (result) => result, + (err) => err +) + +promise.then( + (result) => { f(); return result }, + (err) => { f(); return err } +) + +foo(a => b) +foo(a => { return b }) +foo(c, a => b) +foo(c, a => b, d) +foo(a => b, d) diff --git a/tests/test_declarations/__snapshots__/jsfmt.spec.js.snap b/tests/test_declarations/__snapshots__/jsfmt.spec.js.snap index 46df9e4e..fc01c963 100644 --- a/tests/test_declarations/__snapshots__/jsfmt.spec.js.snap +++ b/tests/test_declarations/__snapshots__/jsfmt.spec.js.snap @@ -34,6 +34,10 @@ test(\`does something really long and complicated so I have to write a very long console.log("hello!"); }); +test("does something really long and complicated so I have to write a very long name for the test", (done) => { + console.log("hello!"); +}); + describe("does something really long and complicated so I have to write a very long name for the describe block", () => { it("an example test", (done) => { console.log("hello!"); @@ -123,6 +127,10 @@ test(\`does something really long and complicated so I have to write a very long console.log("hello!"); }); +test("does something really long and complicated so I have to write a very long name for the test", (done) => { + console.log("hello!"); +}); + describe("does something really long and complicated so I have to write a very long name for the describe block", () => { it("an example test", done => { console.log("hello!"); @@ -190,3 +198,202 @@ it.only.only( ); `; + +exports[`test_declarations.js 2`] = ` +// Shouldn't break + +it("does something really long and complicated so I have to write a very long name for the test", () => { + console.log("hello!"); +}); + +it("does something really long and complicated so I have to write a very long name for the test", function() { + console.log("hello!"); +}); + +it(\`does something really long and complicated so I have to write a very long name for the test\`, function() { + console.log("hello!"); +}); + +it(\`{foo + bar} does something really long and complicated so I have to write a very long name for the test\`, function() { + console.log("hello!"); +}); + +it(\`handles + some + newlines + does something really long and complicated so I have to write a very long name for the test\`, () => { + console.log("hello!"); +}) + +test("does something really long and complicated so I have to write a very long name for the test", (done) => { + console.log("hello!"); +}); + +test(\`does something really long and complicated so I have to write a very long name for the test\`, (done) => { + console.log("hello!"); +}); + +test("does something really long and complicated so I have to write a very long name for the test", (done) => { + console.log("hello!"); +}); + +describe("does something really long and complicated so I have to write a very long name for the describe block", () => { + it("an example test", (done) => { + console.log("hello!"); + }); +}); + +describe(\`does something really long and complicated so I have to write a very long name for the describe block\`, () => { + it(\`an example test\`, (done) => { + console.log("hello!"); + }); +}); + +xdescribe("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +fdescribe("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +describe.only(\`does something really long and complicated so I have to write a very long name for the test\`, () => {}); + +describe.skip(\`does something really long and complicated so I have to write a very long name for the test\`, () => {}); + +fit("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +xit("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +it.only("does something really long and complicated so I have to write a very long name for the test", () => { + console.log("hello!"); +}); + +it.only(\`does something really long and complicated so I have to write a very long name for the test\`, () => { + console.log("hello!"); +}); + +it.skip(\`does something really long and complicated so I have to write a very long name for the test\`, () => {}); + +test.only(\`does something really long and complicated so I have to write a very long name for the test\`, () => {}); + +test.skip(\`does something really long and complicated so I have to write a very long name for the test\`, () => {}); + +ftest("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +xtest("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +// Should break + +it.only("does something really long and complicated so I have to write a very long name for the test", 10, () => { + console.log("hello!"); +}); + +it.only.only("does something really long and complicated so I have to write a very long name for the test", () => { + console.log("hello!"); +}); + +it.only.only("does something really long and complicated so I have to write a very long name for the test", (a, b, c) => { + console.log("hello!"); +}); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Shouldn't break + +it("does something really long and complicated so I have to write a very long name for the test", () => { + console.log("hello!"); +}); + +it("does something really long and complicated so I have to write a very long name for the test", function() { + console.log("hello!"); +}); + +it(\`does something really long and complicated so I have to write a very long name for the test\`, function() { + console.log("hello!"); +}); + +it(\`{foo + bar} does something really long and complicated so I have to write a very long name for the test\`, function() { + console.log("hello!"); +}); + +it(\`handles + some + newlines + does something really long and complicated so I have to write a very long name for the test\`, () => { + console.log("hello!"); +}); + +test("does something really long and complicated so I have to write a very long name for the test", (done) => { + console.log("hello!"); +}); + +test(\`does something really long and complicated so I have to write a very long name for the test\`, (done) => { + console.log("hello!"); +}); + +test("does something really long and complicated so I have to write a very long name for the test", (done) => { + console.log("hello!"); +}); + +describe("does something really long and complicated so I have to write a very long name for the describe block", () => { + it("an example test", (done) => { + console.log("hello!"); + }); +}); + +describe(\`does something really long and complicated so I have to write a very long name for the describe block\`, () => { + it(\`an example test\`, (done) => { + console.log("hello!"); + }); +}); + +xdescribe("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +fdescribe("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +describe.only(\`does something really long and complicated so I have to write a very long name for the test\`, () => {}); + +describe.skip(\`does something really long and complicated so I have to write a very long name for the test\`, () => {}); + +fit("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +xit("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +it.only("does something really long and complicated so I have to write a very long name for the test", () => { + console.log("hello!"); +}); + +it.only(\`does something really long and complicated so I have to write a very long name for the test\`, () => { + console.log("hello!"); +}); + +it.skip(\`does something really long and complicated so I have to write a very long name for the test\`, () => {}); + +test.only(\`does something really long and complicated so I have to write a very long name for the test\`, () => {}); + +test.skip(\`does something really long and complicated so I have to write a very long name for the test\`, () => {}); + +ftest("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +xtest("does something really long and complicated so I have to write a very long name for the describe block", () => {}); + +// Should break + +it.only( + "does something really long and complicated so I have to write a very long name for the test", + 10, + () => { + console.log("hello!"); + } +); + +it.only.only( + "does something really long and complicated so I have to write a very long name for the test", + () => { + console.log("hello!"); + } +); + +it.only.only( + "does something really long and complicated so I have to write a very long name for the test", + (a, b, c) => { + console.log("hello!"); + } +); + +`; diff --git a/tests/test_declarations/jsfmt.spec.js b/tests/test_declarations/jsfmt.spec.js index 7580dfab..d5c0967c 100644 --- a/tests/test_declarations/jsfmt.spec.js +++ b/tests/test_declarations/jsfmt.spec.js @@ -1 +1,2 @@ run_spec(__dirname, null, ["typescript"]); +run_spec(__dirname, { arrowParens: "always" }, ["typescript"]); diff --git a/tests/test_declarations/test_declarations.js b/tests/test_declarations/test_declarations.js index 165f4045..df3253b1 100644 --- a/tests/test_declarations/test_declarations.js +++ b/tests/test_declarations/test_declarations.js @@ -31,6 +31,10 @@ test(`does something really long and complicated so I have to write a very long console.log("hello!"); }); +test("does something really long and complicated so I have to write a very long name for the test", (done) => { + console.log("hello!"); +}); + describe("does something really long and complicated so I have to write a very long name for the describe block", () => { it("an example test", (done) => { console.log("hello!"); diff --git a/tests_integration/__tests__/__snapshots__/early-exit.js.snap b/tests_integration/__tests__/__snapshots__/early-exit.js.snap index 681c3bae..3eafa665 100644 --- a/tests_integration/__tests__/__snapshots__/early-exit.js.snap +++ b/tests_integration/__tests__/__snapshots__/early-exit.js.snap @@ -1,5 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`show detailed usage with --help arrow-parens (stderr) 1`] = `""`; + +exports[`show detailed usage with --help arrow-parens (stdout) 1`] = ` +"--arrow-parens + + Include parentheses around a sole arrow function parameter. + +Valid options: + + avoid Omit parens when possible. Example: \`x => x\` + always Always include parens. Example: \`(x) => x\` + +Default: avoid +" +`; + +exports[`show detailed usage with --help arrow-parens (write) 1`] = `Array []`; + exports[`show detailed usage with --help bracket-spacing (stderr) 1`] = `""`; exports[`show detailed usage with --help bracket-spacing (stdout) 1`] = ` @@ -477,6 +495,9 @@ Output options: Format options: + --arrow-parens + Include parentheses around a sole arrow function parameter. + Defaults to avoid. --no-bracket-spacing Do not print spaces between brackets. --jsx-bracket-same-line Put > on the last line instead of at a new line. Defaults to false. @@ -610,6 +631,9 @@ Output options: Format options: + --arrow-parens + Include parentheses around a sole arrow function parameter. + Defaults to avoid. --no-bracket-spacing Do not print spaces between brackets. --jsx-bracket-same-line Put > on the last line instead of at a new line. Defaults to false.