From 2ef1bc0f8946ffd857f3b03c793da9d1c348bb24 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 27 Nov 2017 08:56:42 +0100 Subject: [PATCH 1/4] Refactor multiparser --- src/multiparser.js | 189 +++++++++++++++++++++++---------------------- src/printer.js | 29 +++---- 2 files changed, 111 insertions(+), 107 deletions(-) diff --git a/src/multiparser.js b/src/multiparser.js index 46941c961a09..bfa01cdb3602 100644 --- a/src/multiparser.js +++ b/src/multiparser.js @@ -9,40 +9,33 @@ const hardline = docBuilders.hardline; const softline = docBuilders.softline; const concat = docBuilders.concat; -function printSubtree(subtreeParser, path, print, options) { - const next = Object.assign({}, { transformDoc: doc => doc }, subtreeParser); - next.options = Object.assign({}, options, next.options, { - parentParser: options.parser, - originalText: next.text - }); - if (next.options.parser === "json") { - next.options.trailingComma = "none"; - } - const ast = require("./parser").parse(next.text, next.options); - const astComments = ast.comments; - delete ast.comments; - comments.attach(astComments, ast, next.text, next.options); - const nextDoc = require("./printer").printAstToDoc(ast, next.options); - return next.transformDoc(nextDoc, { path, print }); -} - -/** - * @returns {{ text, options?, transformDoc? } | void} - */ -function getSubtreeParser(path, options) { +function printSubtree(path, print, options) { switch (options.parser) { case "parse5": - return fromHtmlParser2(path, options); + return fromHtmlParser2(path, print, options); case "babylon": case "flow": case "typescript": - return fromBabylonFlowOrTypeScript(path, options); + return fromBabylonFlowOrTypeScript(path, print, options); case "markdown": - return fromMarkdown(path, options); + return fromMarkdown(path, print, options); } } -function fromMarkdown(path, options) { +function parseAndPrint(passedOptions) { + const options = Object.assign({}, passedOptions, { + parentParser: passedOptions.parser, + trailingComma: + passedOptions.parser === "json" ? "none" : passedOptions.trailingComma + }); + const ast = require("./parser").parse(options.originalText, options); + const astComments = ast.comments; + delete ast.comments; + comments.attach(astComments, ast, options.originalText, options); + return require("./printer").printAstToDoc(ast, options); +} + +function fromMarkdown(path, print, options) { const node = path.getValue(); if (node.type === "code") { @@ -52,11 +45,13 @@ function fromMarkdown(path, options) { const style = styleUnit.repeat( Math.max(3, util.getMaxContinuousCount(node.value, styleUnit) + 1) ); - return { - options: { parser }, - transformDoc: doc => concat([style, node.lang, hardline, doc, style]), - text: node.value - }; + const doc = parseAndPrint( + Object.assign({}, options, { + parser, + originalText: node.value + }) + ); + return concat([style, node.lang, hardline, doc, style]); } } @@ -93,7 +88,7 @@ function fromMarkdown(path, options) { } } -function fromBabylonFlowOrTypeScript(path) { +function fromBabylonFlowOrTypeScript(path, print, options) { const node = path.getValue(); switch (node.type) { @@ -104,11 +99,13 @@ function fromBabylonFlowOrTypeScript(path) { // Get full template literal with expressions replaced by placeholders const rawQuasis = node.quasis.map(q => q.value.raw); const text = rawQuasis.join("@prettier-placeholder"); - return { - options: { parser: "css" }, - transformDoc: transformCssDoc, - text: text - }; + const doc = parseAndPrint( + Object.assign({}, options, { + parser: "css", + originalText: text + }) + ); + return transformCssDoc(doc, path, print); } break; @@ -138,15 +135,16 @@ function fromBabylonFlowOrTypeScript(path) { parentParent.callee.type === "Identifier" && parentParent.callee.name === "graphql")) ) { - return { - options: { parser: "graphql" }, - transformDoc: doc => - concat([ - indent(concat([softline, stripTrailingHardline(doc)])), - softline - ]), - text: parent.quasis[0].value.raw - }; + const doc = parseAndPrint( + Object.assign({}, options, { + parser: "graphql", + originalText: parent.quasis[0].value.raw + }) + ); + return concat([ + indent(concat([softline, stripTrailingHardline(doc)])), + softline + ]); } /** @@ -161,18 +159,20 @@ function fromBabylonFlowOrTypeScript(path) { (parentParent.tag.name === "md" || parentParent.tag.name === "markdown"))) ) { - return { - options: { parser: "markdown", __inJsTemplate: true }, - transformDoc: doc => - concat([ - indent( - concat([softline, stripTrailingHardline(escapeBackticks(doc))]) - ), - softline - ]), - // leading whitespaces matter in markdown - text: dedent(parent.quasis[0].value.cooked) - }; + const doc = parseAndPrint( + Object.assign({}, options, { + parser: "markdown", + __inJsTemplate: true, + // leading whitespaces matter in markdown + originalText: dedent(parent.quasis[0].value.cooked) + }) + ); + return concat([ + indent( + concat([softline, stripTrailingHardline(escapeBackticks(doc))]) + ), + softline + ]); } break; @@ -206,7 +206,7 @@ function escapeBackticks(doc) { }); } -function fromHtmlParser2(path, options) { +function fromHtmlParser2(path, print, options) { const node = path.getValue(); switch (node.type) { @@ -220,11 +220,13 @@ function fromHtmlParser2(path, options) { parent.attribs.type === "application/javascript") ) { const parser = options.parser === "flow" ? "flow" : "babylon"; - return { - options: { parser }, - transformDoc: doc => concat([hardline, doc]), - text: getText(options, node) - }; + const doc = parseAndPrint( + Object.assign({}, options, { + parser, + originalText: getText(options, node) + }) + ); + return concat([hardline, doc]); } // Inline TypeScript @@ -233,20 +235,24 @@ function fromHtmlParser2(path, options) { (parent.attribs.type === "application/x-typescript" || parent.attribs.lang === "ts") ) { - return { - options: { parser: "typescript" }, - transformDoc: doc => concat([hardline, doc]), - text: getText(options, node) - }; + const doc = parseAndPrint( + Object.assign({}, options, { + parser: "typescript", + originalText: getText(options, node) + }) + ); + return concat([hardline, doc]); } // Inline Styles if (parent.type === "style") { - return { - options: { parser: "css" }, - transformDoc: doc => concat([hardline, stripTrailingHardline(doc)]), - text: getText(options, node) - }; + const doc = parseAndPrint( + Object.assign({}, options, { + parser: "css", + originalText: getText(options, node) + }) + ); + return concat([hardline, stripTrailingHardline(doc)]); } break; @@ -261,32 +267,30 @@ function fromHtmlParser2(path, options) { * @click="someFunction()" */ if (/(^@)|(^v-)|:/.test(node.key) && !/^\w+$/.test(node.value)) { - return { - text: node.value, - options: { + const doc = parseAndPrint( + Object.assign({}, options, { parser: parseJavaScriptExpression, // Use singleQuote since HTML attributes use double-quotes. // TODO(azz): We still need to do an entity escape on the attribute. - singleQuote: true - }, - transformDoc: doc => { - return concat([ - node.key, - '="', - util.hasNewlineInRange(node.value, 0, node.value.length) - ? doc - : docUtils.removeLines(doc), - '"' - ]); - } - }; + singleQuote: true, + originalText: node.value + }) + ); + return concat([ + node.key, + '="', + util.hasNewlineInRange(node.value, 0, node.value.length) + ? doc + : docUtils.removeLines(doc), + '"' + ]); } } } } -function transformCssDoc(quasisDoc, parent) { - const parentNode = parent.path.getValue(); +function transformCssDoc(quasisDoc, path, print) { + const parentNode = path.getValue(); const isEmpty = parentNode.quasis.length === 1 && !parentNode.quasis[0].value.raw.trim(); @@ -295,7 +299,7 @@ function transformCssDoc(quasisDoc, parent) { } const expressionDocs = parentNode.expressions - ? parent.path.map(parent.print, "expressions") + ? path.map(print, "expressions") : []; const newDoc = replacePlaceholders(quasisDoc, expressionDocs); /* istanbul ignore if */ @@ -458,6 +462,5 @@ function isStyledIdentifier(node) { } module.exports = { - getSubtreeParser, printSubtree }; diff --git a/src/printer.js b/src/printer.js index edb84906b9f3..35f80421dbe8 100644 --- a/src/printer.js +++ b/src/printer.js @@ -122,20 +122,21 @@ function genericPrint(path, options, printPath, args) { } if (node) { - // Potentially switch to a different parser - const next = multiparser.getSubtreeParser(path, options); - if (next) { - try { - return multiparser.printSubtree(next, path, printPath, options); - } catch (error) { - /* istanbul ignore if */ - if (process.env.PRETTIER_DEBUG) { - const e = new Error(error); - e.parser = next.options.parser; - throw e; - } - // Continue with current parser - } + try { + // Potentially switch to a different parser + const sub = multiparser.printSubtree(path, printPath, options); + if (sub) { + return sub; + } + } catch (error) { + /* istanbul ignore if */ + if (process.env.PRETTIER_DEBUG) { + // TODO + // const e = new Error(error); + // e.parser = next.options.parser; + throw error; + } + // Continue with current parser } } From f3affbaa87764c1cc6a69ce710d5c19701bf8992 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Fri, 1 Dec 2017 10:24:14 +0100 Subject: [PATCH 2/4] Add support for toplevel interpolations in graphql tags --- src/multiparser.js | 148 ++++++++++++++++---- src/printer.js | 3 - tests/multiparser_js_graphql/graphql-tag.js | 127 +++++++++++++++++ 3 files changed, 248 insertions(+), 30 deletions(-) diff --git a/src/multiparser.js b/src/multiparser.js index bfa01cdb3602..54c29090ee88 100644 --- a/src/multiparser.js +++ b/src/multiparser.js @@ -5,6 +5,7 @@ const docUtils = require("./doc-utils"); const docBuilders = require("./doc-builders"); const comments = require("./comments"); const indent = docBuilders.indent; +const join = docBuilders.join; const hardline = docBuilders.hardline; const softline = docBuilders.softline; const concat = docBuilders.concat; @@ -90,6 +91,8 @@ function fromMarkdown(path, print, options) { function fromBabylonFlowOrTypeScript(path, print, options) { const node = path.getValue(); + const parent = path.getParentNode(); + const parentParent = path.getParentNode(1); switch (node.type) { case "TemplateLiteral": { @@ -108,12 +111,6 @@ function fromBabylonFlowOrTypeScript(path, print, options) { return transformCssDoc(doc, path, print); } - break; - } - case "TemplateElement": { - const parent = path.getParentNode(); - const parentParent = path.getParentNode(1); - /* * react-relay and graphql-tag * graphql`...` @@ -121,32 +118,103 @@ function fromBabylonFlowOrTypeScript(path, print, options) { * gql`...` */ if ( - // We currently don't support expression inside GraphQL template literals - parent.expressions.length === 0 && - parentParent && - ((parentParent.type === "TaggedTemplateExpression" && - ((parentParent.tag.type === "MemberExpression" && - parentParent.tag.object.name === "graphql" && - parentParent.tag.property.name === "experimental") || - (parentParent.tag.type === "Identifier" && - (parentParent.tag.name === "gql" || - parentParent.tag.name === "graphql")))) || - (parentParent.type === "CallExpression" && - parentParent.callee.type === "Identifier" && - parentParent.callee.name === "graphql")) + parent && + ((parent.type === "TaggedTemplateExpression" && + ((parent.tag.type === "MemberExpression" && + parent.tag.object.name === "graphql" && + parent.tag.property.name === "experimental") || + (parent.tag.type === "Identifier" && + (parent.tag.name === "gql" || parent.tag.name === "graphql")))) || + (parent.type === "CallExpression" && + parent.callee.type === "Identifier" && + parent.callee.name === "graphql")) ) { - const doc = parseAndPrint( - Object.assign({}, options, { - parser: "graphql", - originalText: parent.quasis[0].value.raw - }) - ); + const expressionDocs = node.expressions + ? path.map(print, "expressions") + : []; + + const numQuasis = node.quasis.length; + + if (numQuasis === 1 && node.quasis[0].value.raw.trim() === "") { + return "``"; + } + + const parts = []; + + for (let i = 0; i < numQuasis; i++) { + const templateElement = node.quasis[i]; + const isFirst = i === 0; + const isLast = i === numQuasis - 1; + const text = templateElement.value.raw; + const lines = text.split("\n"); + const numLines = lines.length; + const expressionDoc = expressionDocs[i]; + + const startsWithBlankLine = + numLines > 2 && lines[0].trim() === "" && lines[1].trim() === ""; + const endsWithBlankLine = + numLines > 2 && + lines[numLines - 1].trim() === "" && + lines[numLines - 2].trim() === ""; + + const commentsAndWhitespaceOnly = lines.every(line => + /^\s*(?:#[^\r\n]*)?$/.test(line) + ); + + // Bail out if an interpolation occurs within a comment. + if (!isLast && /#[^\r\n]*$/.test(lines[numLines - 1])) { + return null; + } + + let doc = null; + + if (commentsAndWhitespaceOnly) { + doc = printGraphqlComments(lines); + } else { + try { + doc = stripTrailingHardline( + parseAndPrint( + Object.assign({}, options, { + parser: "graphql", + originalText: text + }) + ) + ); + } catch (_error) { + // Bail if any part fails to parse. + return null; + } + } + + if (doc) { + if (!isFirst && startsWithBlankLine) { + parts.push(""); + } + parts.push(doc); + if (!isLast && endsWithBlankLine) { + parts.push(""); + } + } else if (!isFirst && !isLast && startsWithBlankLine) { + parts.push(""); + } + + if (expressionDoc) { + parts.push(concat(["${", expressionDoc, "}"])); + } + } + return concat([ - indent(concat([softline, stripTrailingHardline(doc)])), - softline + "`", + indent(concat([hardline, join(hardline, parts)])), + hardline, + "`" ]); } + break; + } + + case "TemplateElement": { /** * md`...` * markdown`...` @@ -180,6 +248,32 @@ function fromBabylonFlowOrTypeScript(path, print, options) { } } +function printGraphqlComments(lines) { + const parts = []; + let seenComment = false; + + lines.map(textLine => textLine.trim()).forEach((textLine, i, array) => { + // Lines are either whitespace only, or a comment (with poential whitespace + // around it). Drop whitespace-only lines. + if (textLine === "") { + return; + } + + if (array[i - 1] === "" && seenComment) { + // If a non-first comment is preceded by a blank (whitespace only) line, + // add in a blank line. + parts.push(concat([hardline, textLine])); + } else { + parts.push(textLine); + } + + seenComment = true; + }); + + // If `lines` was whitespace only, return `null`. + return parts.length === 0 ? null : join(hardline, parts); +} + function dedent(str) { const firstMatchedIndent = str.match(/\n^( *)/m); const spaces = firstMatchedIndent === null ? 0 : firstMatchedIndent[1].length; diff --git a/src/printer.js b/src/printer.js index 35f80421dbe8..9e799fb856f5 100644 --- a/src/printer.js +++ b/src/printer.js @@ -131,9 +131,6 @@ function genericPrint(path, options, printPath, args) { } catch (error) { /* istanbul ignore if */ if (process.env.PRETTIER_DEBUG) { - // TODO - // const e = new Error(error); - // e.parser = next.options.parser; throw error; } // Continue with current parser diff --git a/tests/multiparser_js_graphql/graphql-tag.js b/tests/multiparser_js_graphql/graphql-tag.js index dcf754940102..5324c34a7a72 100644 --- a/tests/multiparser_js_graphql/graphql-tag.js +++ b/tests/multiparser_js_graphql/graphql-tag.js @@ -9,3 +9,130 @@ const query = gql` } } `; + + +// With interpolations: + +gql` +query User { + user(id:5){ + ...UserDetails + ...Friends + } +} + +${USER_DETAILS_FRAGMENT}${FRIENDS_FRAGMENT} +` + + +// Skip if non-toplevel interpolation: + +gql` +query User { + user(id:${id}){ name } +} +` + + +// Skip if top-level interpolation within comment: + +gql` +query User { + user(id:5){ name } +} +#${test} +` + + +// Comment on last line: + +gql` +query User { + user(id:5){ name } +} +# comment` +// ` <-- editor syntax highlighting workaround + + +// Preserve up to one blank line between things and enforce linebreak between +// interpolations: + +gql` +# comment +${one}${two} ${three} +${four} + +${five} +# comment +${six} + +# comment +${seven} +# comment + +${eight} + + # comment with trailing whitespace + + +# blank line above this comment + + +` + + +// Interpolation directly before and after query: + +gql`${one} query Test { test }${two}` + + +// Only interpolation: + +gql`${test}` + + +// Only comment: + +gql`# comment` +// ` <-- editor syntax highlighting workaround + + +// Only whitespace: + +gql` ` + + +// Empty: + +gql`` + + +// Larger mixed test: + +gql` + + + +query User { + test +} + + + +${USER_DETAILS_FRAGMENT} + + # Comment + # that continues on a new line + + + # and has a blank line in the middle + + ${FRIENDS_FRAGMENT} + ${generateFragment({ + totally: "a good idea" + })} + +${fragment}#comment + +fragment another on User { name +}${ fragment }` From 73d11a984b212d11acd5e200f495ce0f03f772f0 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Fri, 1 Dec 2017 10:54:02 +0100 Subject: [PATCH 3/4] Update snapshots --- .../__snapshots__/jsfmt.spec.js.snap | 331 +++++++++++++++++- tests/multiparser_js_graphql/expressions.js | 3 - tests/multiparser_js_graphql/graphql-tag.js | 20 ++ 3 files changed, 333 insertions(+), 21 deletions(-) diff --git a/tests/multiparser_js_graphql/__snapshots__/jsfmt.spec.js.snap b/tests/multiparser_js_graphql/__snapshots__/jsfmt.spec.js.snap index b350be039e3e..580b80d406d8 100644 --- a/tests/multiparser_js_graphql/__snapshots__/jsfmt.spec.js.snap +++ b/tests/multiparser_js_graphql/__snapshots__/jsfmt.spec.js.snap @@ -1,9 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`expressions.js 1`] = ` -// We currently don't support expressions in GraphQL template literals so these templates -// should not change. - graphql(schema, \` query allPartsByManufacturerName($name: String!) { allParts(filter:{manufacturer: {name: $name}}) { @@ -20,29 +17,28 @@ query allPartsByManufacturerName($name: String!) { \${fragments.all} \`) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// We currently don't support expressions in GraphQL template literals so these templates -// should not change. - graphql( schema, \` -query allPartsByManufacturerName($name: String!) { - allParts(filter:{manufacturer: {name: $name}}) { -... PartAll -}} -\${fragments.all} -\` + query allPartsByManufacturerName($name: String!) { + allParts(filter: { manufacturer: { name: $name } }) { + ...PartAll + } + } + \${fragments.all} + \` ); const veryLongVariableNameToMakeTheLineBreak = graphql( schema, \` -query allPartsByManufacturerName($name: String!) { - allParts(filter:{manufacturer: {name: $name}}) { -... PartAll -}} -\${fragments.all} -\` + query allPartsByManufacturerName($name: String!) { + allParts(filter: { manufacturer: { name: $name } }) { + ...PartAll + } + } + \${fragments.all} + \` ); `; @@ -82,6 +78,153 @@ const query = gql\` } } \`; + + +// With interpolations: + +gql\` +query User { + user(id:5){ + ...UserDetails + ...Friends + } +} + +\${USER_DETAILS_FRAGMENT}\${FRIENDS_FRAGMENT} +\` + + +// Skip if non-toplevel interpolation: + +gql\` +query User { + user(id:\${id}){ name } +} +\` + + +// Skip if top-level interpolation within comment: + +gql\` +query User { + user(id:5){ name } +} +#\${test} +\` + + +// Comment on last line: + +gql\` +query User { + user(id:5){ name } +} +# comment\` +// \` <-- editor syntax highlighting workaround + + +// Preserve up to one blank line between things and enforce linebreak between +// interpolations: + +gql\` +# comment +\${one}\${two} \${three} +\${four} + +\${five} +# comment +\${six} + +# comment +\${seven} +# comment + +\${eight} + + # comment with trailing whitespace + + +# blank line above this comment + + +\` + + +// Interpolation directly before and after query: + +gql\`\${one} query Test { test }\${two}\` + + +// Only interpolation: + +gql\`\${test}\` + + +// Only comment: + +gql\`# comment\` +// \` <-- editor syntax highlighting workaround + + +// Only whitespace: + +gql\` \` + + +// Empty: + +gql\`\` + + +// Comments after other things: +// Currently, comments after interpolations are moved to the next line. +// We might want to keep them on the next line in the future. + +gql\` + \${test} # comment + + query Test { # comment + test # comment + } # comment + \${test} # comment + \${test} # comment + + \${test} # comment + + # comment + \${test} # comment +\` + + +// Larger mixed test: + +gql\` + + + +query User { + test +} + + + +\${USER_DETAILS_FRAGMENT} + + # Comment + # that continues on a new line + + + # and has a blank line in the middle + + \${FRIENDS_FRAGMENT} + \${generateFragment({ + totally: "a good idea" + })} + +\${fragment}#comment + +fragment another on User { name +}\${ fragment }\` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ import gql from "graphql-tag"; @@ -95,6 +238,158 @@ const query = gql\` } \`; +// With interpolations: + +gql\` + query User { + user(id: 5) { + ...UserDetails + ...Friends + } + } + + \${USER_DETAILS_FRAGMENT} + \${FRIENDS_FRAGMENT} +\`; + +// Skip if non-toplevel interpolation: + +gql\` +query User { + user(id:\${id}){ name } +} +\`; + +// Skip if top-level interpolation within comment: + +gql\` +query User { + user(id:5){ name } +} +#\${test} +\`; + +// Comment on last line: + +gql\` + query User { + user(id: 5) { + name + } + } + # comment +\`; +// \` <-- editor syntax highlighting workaround + +// Preserve up to one blank line between things and enforce linebreak between +// interpolations: + +gql\` + # comment + \${one} + \${two} + \${three} + \${four} + + \${five} + # comment + \${six} + + # comment + \${seven} + # comment + + \${eight} + + # comment with trailing whitespace + + # blank line above this comment +\`; + +// Interpolation directly before and after query: + +gql\` + \${one} + query Test { + test + } + \${two} +\`; + +// Only interpolation: + +gql\` + \${test} +\`; + +// Only comment: + +gql\` + # comment +\`; +// \` <-- editor syntax highlighting workaround + +// Only whitespace: + +gql\`\`; + +// Empty: + +gql\`\`; + +// Comments after other things: +// Currently, comments after interpolations are moved to the next line. +// We might want to keep them on the next line in the future. + +gql\` + \${test} + # comment + + query Test { + # comment + test # comment + } # comment + \${test} + # comment + \${test} + # comment + + \${test} + # comment + + # comment + \${test} + # comment +\`; + +// Larger mixed test: + +gql\` + query User { + test + } + + \${USER_DETAILS_FRAGMENT} + + # Comment + # that continues on a new line + + # and has a blank line in the middle + + \${FRIENDS_FRAGMENT} + \${generateFragment({ + totally: "a good idea" + })} + + \${fragment} + #comment + + fragment another on User { + name + } + \${fragment} +\`; + `; exports[`react-relay.js 1`] = ` diff --git a/tests/multiparser_js_graphql/expressions.js b/tests/multiparser_js_graphql/expressions.js index 9219dbbe14f1..d3b7f340fe9e 100644 --- a/tests/multiparser_js_graphql/expressions.js +++ b/tests/multiparser_js_graphql/expressions.js @@ -1,6 +1,3 @@ -// We currently don't support expressions in GraphQL template literals so these templates -// should not change. - graphql(schema, ` query allPartsByManufacturerName($name: String!) { allParts(filter:{manufacturer: {name: $name}}) { diff --git a/tests/multiparser_js_graphql/graphql-tag.js b/tests/multiparser_js_graphql/graphql-tag.js index 5324c34a7a72..0bb877c0c4c8 100644 --- a/tests/multiparser_js_graphql/graphql-tag.js +++ b/tests/multiparser_js_graphql/graphql-tag.js @@ -107,6 +107,26 @@ gql` ` gql`` +// Comments after other things: +// Currently, comments after interpolations are moved to the next line. +// We might want to keep them on the next line in the future. + +gql` + ${test} # comment + + query Test { # comment + test # comment + } # comment + ${test} # comment + ${test} # comment + + ${test} # comment + + # comment + ${test} # comment +` + + // Larger mixed test: gql` From 33545fd3637231c30a0a139819fdcc22afe925b1 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Fri, 1 Dec 2017 20:39:51 +0100 Subject: [PATCH 4/4] Fix JSX-in-markdown special-case --- src/multiparser.js | 79 ++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/src/multiparser.js b/src/multiparser.js index 54c29090ee88..ade26630c51e 100644 --- a/src/multiparser.js +++ b/src/multiparser.js @@ -23,17 +23,20 @@ function printSubtree(path, print, options) { } } -function parseAndPrint(passedOptions) { - const options = Object.assign({}, passedOptions, { - parentParser: passedOptions.parser, +function parseAndPrint(text, partialNextOptions, parentOptions) { + const nextOptions = Object.assign({}, parentOptions, partialNextOptions, { + parentParser: parentOptions.parser, trailingComma: - passedOptions.parser === "json" ? "none" : passedOptions.trailingComma + partialNextOptions.parser === "json" + ? "none" + : partialNextOptions.trailingComma, + originalText: text }); - const ast = require("./parser").parse(options.originalText, options); + const ast = require("./parser").parse(text, nextOptions); const astComments = ast.comments; delete ast.comments; - comments.attach(astComments, ast, options.originalText, options); - return require("./printer").printAstToDoc(ast, options); + comments.attach(astComments, ast, text, nextOptions); + return require("./printer").printAstToDoc(ast, nextOptions); } function fromMarkdown(path, print, options) { @@ -46,12 +49,7 @@ function fromMarkdown(path, print, options) { const style = styleUnit.repeat( Math.max(3, util.getMaxContinuousCount(node.value, styleUnit) + 1) ); - const doc = parseAndPrint( - Object.assign({}, options, { - parser, - originalText: node.value - }) - ); + const doc = parseAndPrint(node.value, { parser }, options); return concat([style, node.lang, hardline, doc, style]); } } @@ -102,12 +100,7 @@ function fromBabylonFlowOrTypeScript(path, print, options) { // Get full template literal with expressions replaced by placeholders const rawQuasis = node.quasis.map(q => q.value.raw); const text = rawQuasis.join("@prettier-placeholder"); - const doc = parseAndPrint( - Object.assign({}, options, { - parser: "css", - originalText: text - }) - ); + const doc = parseAndPrint(text, { parser: "css" }, options); return transformCssDoc(doc, path, print); } @@ -173,12 +166,7 @@ function fromBabylonFlowOrTypeScript(path, print, options) { } else { try { doc = stripTrailingHardline( - parseAndPrint( - Object.assign({}, options, { - parser: "graphql", - originalText: text - }) - ) + parseAndPrint(text, { parser: "graphql" }, options) ); } catch (_error) { // Bail if any part fails to parse. @@ -228,12 +216,13 @@ function fromBabylonFlowOrTypeScript(path, print, options) { parentParent.tag.name === "markdown"))) ) { const doc = parseAndPrint( - Object.assign({}, options, { + // leading whitespaces matter in markdown + dedent(parent.quasis[0].value.cooked), + { parser: "markdown", - __inJsTemplate: true, - // leading whitespaces matter in markdown - originalText: dedent(parent.quasis[0].value.cooked) - }) + __inJsTemplate: true + }, + options ); return concat([ indent( @@ -314,12 +303,7 @@ function fromHtmlParser2(path, print, options) { parent.attribs.type === "application/javascript") ) { const parser = options.parser === "flow" ? "flow" : "babylon"; - const doc = parseAndPrint( - Object.assign({}, options, { - parser, - originalText: getText(options, node) - }) - ); + const doc = parseAndPrint(getText(options, node), { parser }, options); return concat([hardline, doc]); } @@ -330,10 +314,9 @@ function fromHtmlParser2(path, print, options) { parent.attribs.lang === "ts") ) { const doc = parseAndPrint( - Object.assign({}, options, { - parser: "typescript", - originalText: getText(options, node) - }) + getText(options, node), + { parser: "typescript" }, + options ); return concat([hardline, doc]); } @@ -341,10 +324,9 @@ function fromHtmlParser2(path, print, options) { // Inline Styles if (parent.type === "style") { const doc = parseAndPrint( - Object.assign({}, options, { - parser: "css", - originalText: getText(options, node) - }) + getText(options, node), + { parser: "css" }, + options ); return concat([hardline, stripTrailingHardline(doc)]); } @@ -362,13 +344,14 @@ function fromHtmlParser2(path, print, options) { */ if (/(^@)|(^v-)|:/.test(node.key) && !/^\w+$/.test(node.value)) { const doc = parseAndPrint( - Object.assign({}, options, { + node.value, + { parser: parseJavaScriptExpression, // Use singleQuote since HTML attributes use double-quotes. // TODO(azz): We still need to do an entity escape on the attribute. - singleQuote: true, - originalText: node.value - }) + singleQuote: true + }, + options ); return concat([ node.key,