src/nodes/arrays.js in prettier-0.21.0 vs src/nodes/arrays.js in prettier-0.22.0

- old
+ new

@@ -1,134 +1,172 @@ const { concat, group, + hardline, ifBreak, indent, join, line, - literalline, softline } = require("../prettier"); -const preserveArraySubstrings = [" ", "\\"]; +// Checks that every argument within this args node is a string_literal node +// that has no spaces or interpolations. This means we're dealing with an array +// that looks something like: +// +// ['a', 'b', 'c'] +// +function isStringArray(args) { + return args.body.every((arg) => { + // We want to verify that every node inside of this array is a string + // literal. We also want to make sure none of them have comments attached. + if (arg.type !== "string_literal" || arg.comments) { + return false; + } -const isStringArray = (args) => - args.body.every( - (arg) => - arg.type === "string_literal" && - arg.body[0].body.length === 1 && - arg.body[0].body[0].type === "@tstring_content" && - !preserveArraySubstrings.some((str) => - arg.body[0].body[0].body.includes(str) - ) - ); + // If the string has multiple parts (meaning plain string content but also + // interpolated content) then we know it's not a simple string. + if (arg.body.length !== 1) { + return false; + } -const isSymbolArray = (args) => - args.body.every((arg) => arg.type === "symbol_literal"); + const part = arg.body[0]; -const makeArray = (start) => (path, opts, print) => - [start].concat(path.map(print, "body")); + // If the only part of this string is not @tstring_content then it's + // interpolated, so again we can return false. + if (part.type !== "@tstring_content") { + return false; + } -const getSpecialArrayParts = (path, print, args) => - args.body.map((_arg, index) => - path.call(print, "body", 0, "body", index, "body", 0, "body", 0) + // Finally, verify that the string doesn't contain a space or an escape + // character so that we know it can be put into a string literal array. + return !part.body.includes(" ") && !part.body.includes("\\"); + }); +} + +// Checks that every argument within this args node is a symbol_literal node (as +// opposed to a dyna_symbol) so it has no interpolation. This means we're +// dealing with an array that looks something like: +// +// [:a, :b, :c] +// +function isSymbolArray(args) { + return args.body.every( + (arg) => arg.type === "symbol_literal" && !arg.comments ); +} -const printSpecialArray = (parts) => - group( +// Prints out a word that is a part of a special array literal that accepts +// interpolation. The body is an array of either plain strings or interpolated +// expressions. +function printSpecialArrayWord(path, opts, print) { + return concat(path.map(print, "body")); +} + +// Prints out a special array literal. Accepts the parts of the array literal as +// an argument, where the first element of the parts array is a string that +// contains the special start. +function printSpecialArrayParts(parts) { + return group( concat([ parts[0], "[", indent(concat([softline, join(line, parts.slice(1))])), concat([softline, "]"]) ]) ); +} -// Extract out the actual elements, taking into account nesting with -// `args_add_star` nodes. The only nodes that get passed into this function are -// `args` or `args_add_star`. -const getElements = (node, elementPath) => { - if (node.type === "args") { - return node.body.map((element, index) => ({ - element, - elementPath: elementPath.concat(["body", index]) - })); - } +// Generates a print function with an embedded special start character for the +// specific type of array literal that we're dealing with. The print function +// returns an array as it expects to eventually be handed off to +// printSpecialArrayParts. +function printSpecialArray(start) { + return function printSpecialArrayWithStart(path, opts, print) { + return [start].concat(path.map(print, "body")); + }; +} - return getElements(node.body[0], elementPath.concat(["body", 0])).concat( - node.body.slice(1).map((element, index) => ({ - element, - elementPath: elementPath.concat(["body", index + 1]) - })) - ); -}; +function printEmptyArrayWithComments(path, opts) { + const arrayNode = path.getValue(); -module.exports = { - array: (path, { addTrailingCommas }, print) => { - const args = path.getValue().body[0]; + const printComment = (commentPath, index) => { + arrayNode.comments[index].printed = true; + return opts.printer.printComment(commentPath); + }; - if (args === null) { - return "[]"; - } + return concat([ + "[", + indent( + concat([hardline, join(hardline, path.map(printComment, "comments"))]) + ), + line, + "]" + ]); +} - if (isStringArray(args)) { - return printSpecialArray( - ["%w"].concat(getSpecialArrayParts(path, print, args)) - ); - } +// An array node is any literal array in Ruby. This includes all of the special +// array literals as well as regular arrays. If it is a special array literal +// then it will have one child that represents the special array, otherwise it +// will have one child that contains all of the elements of the array. +function printArray(path, opts, print) { + const array = path.getValue(); + const args = array.body[0]; - if (isSymbolArray(args)) { - return printSpecialArray( - ["%i"].concat(getSpecialArrayParts(path, print, args)) - ); - } + // If there is no inner arguments node, then we're dealing with an empty + // array, so we can go ahead and return. + if (args === null) { + return array.comments ? printEmptyArrayWithComments(path, opts) : "[]"; + } - if (!["args", "args_add_star"].includes(args.type)) { - return printSpecialArray(path.call(print, "body", 0)); - } + // If we have an array that contains only simple string literals with no + // spaces or interpolation, then we're going to print a %w array. + if (isStringArray(args)) { + const printString = (stringPath) => stringPath.call(print, "body", 0); + const parts = path.map(printString, "body", 0, "body"); - const normalDocs = []; + return printSpecialArrayParts(["%w"].concat(parts)); + } - const elementDocs = path.call(print, "body", 0); - const elements = getElements(path.getValue().body[0], ["body", 0]); + // If we have an array that contains only simple symbol literals with no + // interpolation, then we're going to print a %i array. + if (isSymbolArray(args)) { + const printSymbol = (symbolPath) => symbolPath.call(print, "body", 0); + const parts = path.map(printSymbol, "body", 0, "body"); - // We need to manually loop through the elements in the array in order to - // take care of heredocs printing (their commas go after the opening, as - // opposed to at the end). - elements.forEach(({ element, elementPath }, index) => { - const isInner = index !== elements.length - 1; + return printSpecialArrayParts(["%i"].concat(parts)); + } - if (element.type === "heredoc") { - normalDocs.push( - element.beging, - isInner || addTrailingCommas ? "," : "", - literalline, - concat( - path.map.apply(path, [print].concat(elementPath).concat("body")) - ), - element.ending, - isInner ? line : "" - ); - } else { - normalDocs.push(elementDocs[index]); + // If we don't have a regular args node at this point then we have a special + // array literal. In that case we're going to print out the body (which will + // return to us an array with the first one being the start of the array) and + // send that over to the printSpecialArrayParts function. + if (!["args", "args_add_star"].includes(args.type)) { + return printSpecialArrayParts(path.call(print, "body", 0)); + } - if (isInner) { - normalDocs.push(concat([",", line])); - } else if (addTrailingCommas) { - normalDocs.push(ifBreak(",", "")); - } - } - }); + // Here we have a normal array of any type of object with no special literal + // types or anything. + return group( + concat([ + "[", + indent( + concat([ + softline, + join(concat([",", line]), path.call(print, "body", 0)), + opts.addTrailingCommas ? ifBreak(",", "") : "" + ]) + ), + softline, + "]" + ]) + ); +} - return group( - concat([ - "[", - indent(concat([softline].concat(normalDocs))), - concat([softline, "]"]) - ]) - ); - }, - qsymbols: makeArray("%i"), - qwords: makeArray("%w"), - symbols: makeArray("%I"), - words: makeArray("%W") +module.exports = { + array: printArray, + qsymbols: printSpecialArray("%i"), + qwords: printSpecialArray("%w"), + symbols: printSpecialArray("%I"), + word: printSpecialArrayWord, + words: printSpecialArray("%W") };