diff --git a/src/doc-printer.js b/src/doc-printer.js index 7e5acad25..ac6747ec1 100644 --- a/src/doc-printer.js +++ b/src/doc-printer.js @@ -21,6 +21,16 @@ function makeIndent(ind) { } function makeAlign(ind, n) { + if (n === -Infinity) { + return { + indent: 0, + align: { + spaces: 0, + tabs: 0 + } + }; + } + return { indent: ind.indent, align: { diff --git a/src/printer.js b/src/printer.js index 43e0c8c81..13c69b39d 100644 --- a/src/printer.js +++ b/src/printer.js @@ -1506,9 +1506,51 @@ function genericPrintNoParens(path, options, print, args) { parts.push(print(childPath)); if (i < expressions.length) { + // For a template literal of the following form: + // `someQuery { + // ${call({ + // a, + // b, + // })} + // }` + // the expression is on its own line (there is a \n in the previous + // quasi literal), therefore we want to indent the JavaScript + // expression inside at the beginning of ${ instead of the beginning + // of the `. + let size = 0; + const value = childPath.getValue().value.raw; + const index = value.lastIndexOf('\n'); + const tabWidth = options.tabWidth; + if (index !== -1) { + for (let i = index + 1; i < value.length; ++i) { + if (value[i] === '\t') { + // Tabs behave in a way that they are aligned to the nearest + // multiple of tabWidth: + // 0 -> 4, 1 -> 4, 2 -> 4, 3 -> 4 + // 4 -> 8, 5 -> 8, 6 -> 8, 7 -> 8 ... + size = size + tabWidth - size % tabWidth; + } else { + size++; + } + } + } + + let aligned = removeLines(expressions[i]); + if (size > 0) { + // Use indent to add tabs for all the levels of tabs we need + for (var i = 0; i < Math.floor(size / tabWidth); ++i) { + aligned = indent(aligned); + } + // Use align for all the spaces that are needed + aligned = align(size % tabWidth, aligned); + // size is absolute from 0 and not relative to the current + // indentation, so we use -Infinity to reset the indentation to 0 + aligned = align(-Infinity, aligned); + } + parts.push( "${", - removeLines(expressions[i]), + aligned, lineSuffixBoundary, "}" ); diff --git a/tests/template/__snapshots__/jsfmt.spec.js.snap b/tests/template/__snapshots__/jsfmt.spec.js.snap index 666508149..0c08522dc 100644 --- a/tests/template/__snapshots__/jsfmt.spec.js.snap +++ b/tests/template/__snapshots__/jsfmt.spec.js.snap @@ -82,6 +82,45 @@ var x = { `; +exports[`graphql.js 1`] = ` +module.exports = Relay.createContainer( + // ... + { + fragments: { + nodes: ({solution_type, time_frame}) => Relay.QL\` + fragment on RelatedNode @relay(plural: true) { + __typename + \${OptimalSolutionsSection + .getFragment( + 'node', + {solution_type, time_frame}, + ) + } + } + \`, + }, + }, +); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +module.exports = Relay.createContainer( + // ... + { + fragments: { + nodes: ({ solution_type, time_frame }) => Relay.QL\` + fragment on RelatedNode @relay(plural: true) { + __typename + \${OptimalSolutionsSection.getFragment("node", { + solution_type, + time_frame + })} + } + \` + } + } +); + +`; + exports[`parenthesis.js 1`] = ` // "ArrowFunctionExpression" (() => {})\`\`; diff --git a/tests/template/graphql.js b/tests/template/graphql.js new file mode 100644 index 000000000..9a36a652d --- /dev/null +++ b/tests/template/graphql.js @@ -0,0 +1,18 @@ +module.exports = Relay.createContainer( + // ... + { + fragments: { + nodes: ({solution_type, time_frame}) => Relay.QL` + fragment on RelatedNode @relay(plural: true) { + __typename + ${OptimalSolutionsSection + .getFragment( + 'node', + {solution_type, time_frame}, + ) + } + } + `, + }, + }, +); diff --git a/tests/template_align/__snapshots__/jsfmt.spec.js.snap b/tests/template_align/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..ce3294422 --- /dev/null +++ b/tests/template_align/__snapshots__/jsfmt.spec.js.snap @@ -0,0 +1,119 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`indent.js 1`] = ` +\` +Mixed tabs and characters: +\${() => {a}} +. \${() => {a}} +.. \${() => {a}} +... \${() => {a}} +.... \${() => {a}} +..... \${() => {a}} +...... \${() => {a}} +....... \${() => {a}} +........ \${() => {a}} + +Tabs: + \${() => {a}} + \${() => {a}} +\` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\` +Mixed tabs and characters: +\${() => { + a; +}} +. \${() => { + a; + }} +.. \${() => { + a; + }} +... \${() => { + a; + }} +.... \${() => { + a; + }} +..... \${() => { + a; + }} +...... \${() => { + a; + }} +....... \${() => { + a; + }} +........ \${() => { + a; + }} + +Tabs: + \${() => { + a; + }} + \${() => { + a; + }} +\`; + +`; + +exports[`indent.js 2`] = ` +\` +Mixed tabs and characters: +\${() => {a}} +. \${() => {a}} +.. \${() => {a}} +... \${() => {a}} +.... \${() => {a}} +..... \${() => {a}} +...... \${() => {a}} +....... \${() => {a}} +........ \${() => {a}} + +Tabs: + \${() => {a}} + \${() => {a}} +\` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +\` +Mixed tabs and characters: +\${() => { + a; +}} +. \${() => { + a; + }} +.. \${() => { + a; + }} +... \${() => { + a; + }} +.... \${() => { + a; + }} +..... \${() => { + a; + }} +...... \${() => { + a; + }} +....... \${() => { + a; + }} +........ \${() => { + a; + }} + +Tabs: + \${() => { + a; + }} + \${() => { + a; + }} +\`; + +`; diff --git a/tests/template_align/indent.js b/tests/template_align/indent.js new file mode 100644 index 000000000..5793e4118 --- /dev/null +++ b/tests/template_align/indent.js @@ -0,0 +1,16 @@ +` +Mixed tabs and characters: +${() => {a}} +. ${() => {a}} +.. ${() => {a}} +... ${() => {a}} +.... ${() => {a}} +..... ${() => {a}} +...... ${() => {a}} +....... ${() => {a}} +........ ${() => {a}} + +Tabs: + ${() => {a}} + ${() => {a}} +` diff --git a/tests/template_align/jsfmt.spec.js b/tests/template_align/jsfmt.spec.js new file mode 100644 index 000000000..2e6e1e55e --- /dev/null +++ b/tests/template_align/jsfmt.spec.js @@ -0,0 +1,2 @@ +run_spec(__dirname, null, ["typescript"]); +run_spec(__dirname, {useTabs: true, tabWidth: 8}, ["typescript"]);