diff --git a/src/doc-builders.js b/src/doc-builders.js index 82c7221bbe06..a9840b5392e8 100644 --- a/src/doc-builders.js +++ b/src/doc-builders.js @@ -51,6 +51,12 @@ function conditionalGroup(states, opts) { ); } +function fill(parts) { + parts.forEach(assertDoc); + + return { type: "fill", parts }; +} + function ifBreak(breakContents, flatContents) { if (breakContents) { assertDoc(breakContents); @@ -100,6 +106,7 @@ module.exports = { literalline, group, conditionalGroup, + fill, lineSuffix, lineSuffixBoundary, breakParent, diff --git a/src/doc-debug.js b/src/doc-debug.js index 79b4a3c5ac94..6576786835f7 100644 --- a/src/doc-debug.js +++ b/src/doc-debug.js @@ -91,10 +91,21 @@ function printDoc(doc) { ")"; } + if (doc.type === "fill") { + return ("fill") + + "(" + + doc.parts.map(printDoc).join(", ") + + ")"; + } + if (doc.type === "line-suffix") { return "lineSuffix(" + printDoc(doc.contents) + ")"; } + if (doc.type === "line-suffix-boundary") { + return "lineSuffixBoundary()"; + } + throw new Error("Unknown doc type " + doc.type); } diff --git a/src/doc-printer.js b/src/doc-printer.js index 82b493026d3e..b80ec70d99b8 100644 --- a/src/doc-printer.js +++ b/src/doc-printer.js @@ -1,9 +1,16 @@ "use strict"; +var docBuilders = require("./doc-builders"); +var concat = docBuilders.concat; +var line = docBuilders.line; +var softline = docBuilders.softline; +var fill = docBuilders.fill; +var ifBreak = docBuilders.ifBreak; + const MODE_BREAK = 1; const MODE_FLAT = 2; -function fits(next, restCommands, width) { +function fits(next, restCommands, width, mustBeFlat = false) { let restIdx = restCommands.length; const cmds = [next]; while (width >= 0) { @@ -39,8 +46,19 @@ function fits(next, restCommands, width) { break; case "group": + if (mustBeFlat && doc.break) { + return false; + } cmds.push([ind, doc.break ? MODE_BREAK : mode, doc.contents]); + break; + case "fill": + // include number of spaces + cmds.push([ind, mode, " ".repeat(doc.parts.length - 1)]); + for (var i = doc.parts.length - 1; i >= 0; i--) { + cmds.push([ind, mode, doc.parts[i]]); + } + break; case "if-break": if (mode === MODE_BREAK) { @@ -176,6 +194,51 @@ function printDocToString(doc, width, newLine) { break; } break; + case "fill": + // shouldRemeasure = false; + let rem = width - pos; + + const parts = doc.parts; + if (parts.length === 1) { + cmds.push([ind, MODE_BREAK, parts[0]]); + break; + } + + const first = parts[0]; + const second = parts[1]; + const remaining = parts.slice(1); + const firstCmd = [ind, MODE_FLAT, first]; + const remainingCmd = [ind, MODE_BREAK, fill(remaining)]; + + const firstAndSecondCmd = [ind, MODE_FLAT, concat([first, second])]; + + if (fits(firstAndSecondCmd, cmds, rem, true)) { + // console.log('first and second fit', [first, second]); + cmds.push(remainingCmd) + cmds.push([ind, MODE_FLAT, line]); + cmds.push(firstCmd); + } else if (fits(firstCmd, cmds, width - rem, true)) { + // console.log('only first fits', [first, second]); + cmds.push(remainingCmd) + if (typeof second !== 'string' || typeof first !== 'string') { + cmds.push([ind, MODE_BREAK, "{' '}"]); + } + cmds.push([ind, MODE_BREAK, line]); + cmds.push(firstCmd); + } else { + // console.log('neither fit', [first, second]); + out.push(newLine + " ".repeat(ind)); + pos = ind; + + cmds.push(remainingCmd); + if (typeof first !== 'string') { + cmds.push([ind, MODE_BREAK, line]); + cmds.push([ind, MODE_BREAK, "{' '}"]); + } + cmds.push([ind, MODE_BREAK, line]); + cmds.push([ind, MODE_BREAK, first]); + } + break; case "if-break": if (mode === MODE_BREAK) { if (doc.breakContents) { diff --git a/src/printer.js b/src/printer.js index 42354ddf2b5a..092d0b7c4bd2 100644 --- a/src/printer.js +++ b/src/printer.js @@ -16,6 +16,7 @@ var literalline = docBuilders.literalline; var group = docBuilders.group; var indent = docBuilders.indent; var conditionalGroup = docBuilders.conditionalGroup; +var fill = docBuilders.fill; var ifBreak = docBuilders.ifBreak; var breakParent = docBuilders.breakParent; var lineSuffixBoundary = docBuilders.lineSuffixBoundary; @@ -655,7 +656,7 @@ function genericPrintNoParens(path, options, print) { var isTypeAnnotation = n.type === "ObjectTypeAnnotation"; var isTypeScriptTypeAnnotaion = n.type === "TSTypeLiteral"; // Leave this here because we *might* want to make this - // configurable later -- flow accepts ";" for type separators, + // configurable later -- flow accepts ";" for type separators, // typescript accepts ";" and newlines var separator = isTypeAnnotation ? "," : ","; var fields = []; @@ -1069,7 +1070,7 @@ function genericPrintNoParens(path, options, print) { case "ForOfStatement": case "ForAwaitStatement": - // Babylon 7 removed ForAwaitStatement in favor of ForOfStatement + // Babylon 7 removed ForAwaitStatement in favor of ForOfStatement // with `"await": true`: // https://github.com/estree/estree/pull/138 const isAwait = (n.type === "ForAwaitStatement" || n.await); @@ -1853,8 +1854,8 @@ function genericPrintNoParens(path, options, print) { return "void"; case "TSAsExpression": return concat([ - path.call(print, "expression"), - " as ", + path.call(print, "expression"), + " as ", path.call(print, "typeAnnotation"), ]) case "TSArrayType": @@ -1868,17 +1869,17 @@ function genericPrintNoParens(path, options, print) { return concat([path.call(print, "typeName")]); case "TSCallSignature": return concat([ - "(", - join(", ", path.map(print, "parameters")), + "(", + join(", ", path.map(print, "parameters")), "): ", - path.call(print, "typeAnnotation"), + path.call(print, "typeAnnotation"), ]); case "TSConstructSignature": return concat([ - "new (", - join(", ", path.map(print, "parameters")), + "new (", + join(", ", path.map(print, "parameters")), "): ", - path.call(print, "typeAnnotation"), + path.call(print, "typeAnnotation"), ]); case "TSTypeQuery": return concat(["typeof ", path.call(print, "exprName")]); @@ -1886,12 +1887,12 @@ function genericPrintNoParens(path, options, print) { return concat(["(", path.call(print, "typeAnnotation"), ")"]); case "TSIndexSignature": return concat([ - "[", + "[", // This should only contain a single element, however TypeScript parses // it using parseDelimitedList that uses commas as delimiter. - join(", ", path.map(print, "parameters")), + join(", ", path.map(print, "parameters")), "]: ", - path.call(print, "typeAnnotation"), + path.call(print, "typeAnnotation"), ]); // TODO case "ClassHeritage": @@ -2762,62 +2763,83 @@ function printJSXChildren(path, options, print, jsxWhitespace) { : util.htmlEscapeInsideAngleBracket(child.value); const value = partiallyEscapedValue.replace(/\u00a0/g, " "); - if (/\S/.test(value)) { - // treat each line of text as its own entity - value.split(/(\n\s*)/).forEach(line => { - const newlines = line.match(/\n/g); - if (newlines) { - children.push(hardline); - - // allow one extra newline - if (newlines.length > 1) { - children.push(hardline); - } - return; - } - const beginSpace = /^\s+/.test(line); - if (beginSpace) { - children.push(jsxWhitespace); - children.push(softline); - } + // // console.log('value', value); + // if (/\S/.test(value)) { + // value.split(/(\s+)/).forEach(word => { + // // const newlines = word.match(/\n/g); + // // if (newlines) { + // // children.push(hardline); + // // + // // // allow one extra newline + // // if (newlines.length > 1) { + // // children.push(hardline); + // // } + // // return; + // // } + // + // const space = /^\s+$/.test(word); + // if (space) { + // words.push(line); + // return; + // } + // + // const stripped = word.replace(/^\s+|\s+$/g, ""); + // if (stripped) { + // words.push(stripped); + // } + // }); + // + // if (!isLineNext(util.getLast(words))) { + // words.push(softline); + // } + // } else if (/\n/.test(value)) { + // words.push(hardline); + // + // // allow one extra newline + // if (value.match(/\n/g).length > 1) { + // words.push(hardline); + // } + // } else if (/\s/.test(value)) { + // // whitespace-only without newlines, + // // eg; a single space separating two elements + // words.push(jsxWhitespace); + // words.push(softline); + // } + + value.replace(/^\s+|\s+$/g, "").split(/(\s+)/).forEach(word => { + if (word.length === 0) { + return; + } - const stripped = line.replace(/^\s+|\s+$/g, ""); - if (stripped) { - children.push(stripped); - } + const newlines = word.match(/\n/g); + if (newlines) { + children.push(hardline); - const endSpace = /\s+$/.test(line); - if (endSpace) { - children.push(softline); - children.push(jsxWhitespace); + // allow one extra newline + if (newlines.length > 1) { + children.push(hardline); } - }); - - if (!isLineNext(util.getLast(children))) { - children.push(softline); + return; } - } else if (/\n/.test(value)) { - children.push(hardline); - // allow one extra newline - if (value.match(/\n/g).length > 1) { - children.push(hardline); + const space = /^\s+$/.test(word); + if (space) { + // children.push(line); + return; } - } else if (/\s/.test(value)) { - // whitespace-only without newlines, - // eg; a single space separating two elements - children.push(jsxWhitespace); - children.push(softline); - } + + children.push(word); + }); + } else { children.push(print(childPath)); - - // add a line unless it's followed by a JSX newline - let next = n.children[i + 1]; - if (!(next && /^\s*\n/.test(next.value))) { - children.push(softline); - } + // + // // add a line unless it's followed by a JSX newline + // let next = n.children[i + 1]; + // if (!(next && /^\s*\n/.test(next.value))) { + // children.push(softline); + // } } }, "children" @@ -2932,7 +2954,7 @@ function printJSXElement(path, options, print) { groups.map( contents => Array.isArray(contents) - ? conditionalGroup([concat(contents)]) + ? fill(contents) : contents ) ) @@ -2957,7 +2979,7 @@ function printJSXElement(path, options, print) { } return conditionalGroup([ - group(concat([openingLines, concat(children), closingLines])), + group(concat([openingLines, fill(children), closingLines])), multiLineElem ]); } diff --git a/tests/jsx-significant-space/__snapshots__/jsfmt.spec.js.snap b/tests/jsx-significant-space/__snapshots__/jsfmt.spec.js.snap index 36c0f42c95dd..92faed0c2456 100644 --- a/tests/jsx-significant-space/__snapshots__/jsfmt.spec.js.snap +++ b/tests/jsx-significant-space/__snapshots__/jsfmt.spec.js.snap @@ -1,199 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`test.js 1`] = ` -"after = - - foo bar - - -before = - - bar foo - - -before_break1 = - - foo - - -before_break2 = - - foo - - -after_break = - - foo - - -within = - - foo bar - - -break_components = +"x =
foobar bar bar
foobar bar bar
foobar bar bar