prettier 0.17.0 β†’ 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -107,7 +107,7 @@ Once every node has been printed like the `binary` node above, the `print` node
107
107
 
108
108
  ### Doc to text
109
109
 
110
- At this point, this is where `prettier`'s printer takes over. Because the remainder of the process is language-agnostic and `prettier` knows how to handle its own `Doc` representation, the Ruby plugin now longer has a job to do. `prettier` will walk its own `Doc` nodes and print them out according to the rules established by the structure.
110
+ At this point, this is where `prettier`'s printer takes over. Because the remainder of the process is language-agnostic and `prettier` knows how to handle its own `Doc` representation, the Ruby plugin no longer has a job to do. `prettier` will walk its own `Doc` nodes and print them out according to the rules established by the structure.
111
111
 
112
112
  ## FAQ
113
113
 
data/README.md CHANGED
@@ -8,8 +8,8 @@
8
8
  <a href="https://gitter.im/jlongster/prettier">
9
9
  <img alt="Gitter" src="https://img.shields.io/gitter/room/jlongster/prettier.svg?style=flat-square">
10
10
  </a>
11
- <a href="https://travis-ci.org/prettier/plugin-ruby">
12
- <img alt="Travis" src="https://img.shields.io/travis/prettier/plugin-ruby/master.svg?style=flat-square">
11
+ <a href="https://github.com/prettier/plugin-ruby/actions">
12
+ <img alt="GitHub Actions" src="https://img.shields.io/github/workflow/status/prettier/plugin-ruby/Main?style=flat-square">
13
13
  </a>
14
14
  <a href="https://www.npmjs.com/package/@prettier/plugin-ruby">
15
15
  <img alt="NPM Version" src="https://img.shields.io/npm/v/@prettier/plugin-ruby.svg?style=flat-square">
@@ -120,16 +120,16 @@ The `prettier` executable is now installed and ready for use:
120
120
 
121
121
  Below are the options (from [`src/ruby.js`](src/ruby.js)) that `@prettier/plugin-ruby` currently supports:
122
122
 
123
- | Name | Default | Description |
124
- | -------------------- | :-----: | ------------------------------------------------------------------------------------------------------------- |
125
- | `printWidth` | `80` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#print-width)). |
126
- | `requirePragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#require-pragma)). |
127
- | `tabWidth` | `2` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tab-width)). |
128
- | `addTrailingCommas` | `false` | Adds a trailing comma to array literals, hash literals, and method calls. |
129
- | `inlineConditionals` | `true` | When it fits on one line, allows if and unless statements to use the modifier form. |
130
- | `inlineLoops` | `true` | When it fits on one line, allows while and until statements to use the modifier form. |
131
- | `preferHashLabels` | `true` | When possible, uses the shortened hash key syntax, as opposed to hash rockets. |
132
- | `preferSingleQuotes` | `true` | When double quotes are not necessary for interpolation, prefers the use of single quotes for string literals. |
123
+ | API Option | CLI Option | Default | Description |
124
+ | -------------------- | ------------------------ | :-----: | ------------------------------------------------------------------------------------------------------------- |
125
+ | `printWidth` | `--print-width` | `80` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#print-width)). |
126
+ | `requirePragma` | `--require-pragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#require-pragma)). |
127
+ | `tabWidth` | `--tab-width` | `2` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tab-width)). |
128
+ | `addTrailingCommas` | `--add-trailing-commas` | `false` | Adds a trailing comma to array literals, hash literals, and method calls. |
129
+ | `inlineConditionals` | `--inline-conditionals` | `true` | When it fits on one line, allows if and unless statements to use the modifier form. |
130
+ | `inlineLoops` | `--inline-loops` | `true` | When it fits on one line, allows while and until statements to use the modifier form. |
131
+ | `preferHashLabels` | `--prefer-hash-labels` | `true` | When possible, uses the shortened hash key syntax, as opposed to hash rockets. |
132
+ | `preferSingleQuotes` | `--prefer-single-quotes` | `true` | When double quotes are not necessary for interpolation, prefers the use of single quotes for string literals. |
133
133
 
134
134
  Any of these can be added to your existing [prettier configuration
135
135
  file](https://prettier.io/docs/en/configuration.html). For example:
@@ -199,6 +199,15 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
199
199
  <td align="center"><a href="http://vesalaakso.com"><img src="https://avatars2.githubusercontent.com/u/482561?v=4" width="100px;" alt=""/><br /><sub><b>Vesa Laakso</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/commits?author=valscion" title="Documentation">πŸ“–</a></td>
200
200
  <td align="center"><a href="https://github.com/jrdioko"><img src="https://avatars0.githubusercontent.com/u/405288?v=4" width="100px;" alt=""/><br /><sub><b>jrdioko</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/issues?q=author%3Ajrdioko" title="Bug reports">πŸ›</a></td>
201
201
  <td align="center"><a href="http://gin0606.hatenablog.com"><img src="https://avatars2.githubusercontent.com/u/795891?v=4" width="100px;" alt=""/><br /><sub><b>gin0606</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/issues?q=author%3Agin0606" title="Bug reports">πŸ›</a> <a href="https://github.com/prettier/plugin-ruby/commits?author=gin0606" title="Code">πŸ’»</a></td>
202
+ <td align="center"><a href="https://github.com/tobyndockerill"><img src="https://avatars1.githubusercontent.com/u/5688326?v=4" width="100px;" alt=""/><br /><sub><b>Tobyn</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/commits?author=tobyndockerill" title="Code">πŸ’»</a></td>
203
+ <td align="center"><a href="http://ianks.com"><img src="https://avatars0.githubusercontent.com/u/3303032?v=4" width="100px;" alt=""/><br /><sub><b>Ian Ker-Seymer</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/commits?author=ianks" title="Code">πŸ’»</a></td>
204
+ <td align="center"><a href="https://huangzhimin.com"><img src="https://avatars2.githubusercontent.com/u/66836?v=4" width="100px;" alt=""/><br /><sub><b>Richard Huang</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/commits?author=flyerhzm" title="Code">πŸ’»</a></td>
205
+ </tr>
206
+ <tr>
207
+ <td align="center"><a href="https://github.com/pje"><img src="https://avatars1.githubusercontent.com/u/319655?v=4" width="100px;" alt=""/><br /><sub><b>Patrick Ellis</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/issues?q=author%3Apje" title="Bug reports">πŸ›</a></td>
208
+ <td align="center"><a href="https://www.piesync.com/"><img src="https://avatars0.githubusercontent.com/u/161271?v=4" width="100px;" alt=""/><br /><sub><b>Peter De Berdt</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/issues?q=author%3Amasqita" title="Bug reports">πŸ›</a></td>
209
+ <td align="center"><a href="https://github.com/hafley66"><img src="https://avatars0.githubusercontent.com/u/6750483?v=4" width="100px;" alt=""/><br /><sub><b>Chris Hafley</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/issues?q=author%3Ahafley66" title="Bug reports">πŸ›</a></td>
210
+ <td align="center"><a href="http://fruetel.de"><img src="https://avatars1.githubusercontent.com/u/8015212?v=4" width="100px;" alt=""/><br /><sub><b>Thomas FrΓΌtel</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/issues?q=author%3AFruetel" title="Bug reports">πŸ›</a></td>
202
211
  </tr>
203
212
  </table>
204
213
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prettier/plugin-ruby",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "prettier plugin for the Ruby programming language",
5
5
  "main": "src/ruby.js",
6
6
  "scripts": {
@@ -22,12 +22,12 @@
22
22
  "prettier": ">=1.10"
23
23
  },
24
24
  "devDependencies": {
25
- "all-contributors-cli": "^6.1.2",
26
- "eslint": "^6.0.1",
25
+ "all-contributors-cli": "^6.12.0",
26
+ "eslint": "^6.8.0",
27
27
  "eslint-config-airbnb-base": "^14.0.0",
28
- "eslint-config-prettier": "^6.0.0",
29
- "eslint-plugin-import": "^2.16.0",
30
- "jest": "^24.0.0"
28
+ "eslint-config-prettier": "^6.9.0",
29
+ "eslint-plugin-import": "^2.20.0",
30
+ "jest": "^25.1.0"
31
31
  },
32
32
  "jest": {
33
33
  "setupFilesAfterEnv": [
@@ -1,5 +1,21 @@
1
1
  const { concat, group, hardline, indent, join } = require("../../prettier");
2
2
 
3
+ const findKeywordIndices = (children, keywords) => {
4
+ const indices = [];
5
+
6
+ children.forEach((child, index) => {
7
+ if (child.type !== "silent_script") {
8
+ return;
9
+ }
10
+
11
+ if (keywords.includes(child.value.keyword)) {
12
+ indices.push(index);
13
+ }
14
+ });
15
+
16
+ return indices;
17
+ };
18
+
3
19
  // http://haml.info/docs/yardoc/file.REFERENCE.html#running-ruby--
4
20
  const silentScript = (path, opts, print) => {
5
21
  const { children, value } = path.getValue();
@@ -9,13 +25,26 @@ const silentScript = (path, opts, print) => {
9
25
  const scripts = path.map(print, "children");
10
26
 
11
27
  if (value.keyword === "case") {
28
+ const keywordIndices = findKeywordIndices(children, ["when", "else"]);
29
+
30
+ parts.push(
31
+ concat(
32
+ scripts.map((script, index) => {
33
+ const concated = concat([hardline, script]);
34
+
35
+ return keywordIndices.includes(index) ? concated : indent(concated);
36
+ })
37
+ )
38
+ );
39
+ } else if (["if", "unless"].includes(value.keyword)) {
40
+ const keywordIndices = findKeywordIndices(children, ["elsif", "else"]);
41
+
12
42
  parts.push(
13
- join(
14
- "",
43
+ concat(
15
44
  scripts.map((script, index) => {
16
45
  const concated = concat([hardline, script]);
17
46
 
18
- return index % 2 === 0 ? concated : indent(concated);
47
+ return keywordIndices.includes(index) ? concated : indent(concated);
19
48
  })
20
49
  )
21
50
  );
@@ -71,7 +71,7 @@ const getHeader = (value, opts) => {
71
71
  }
72
72
 
73
73
  if (attributes.class) {
74
- parts.push(`.${attributes.class.replace(" ", ".")}`);
74
+ parts.push(`.${attributes.class.replace(/ /g, ".")}`);
75
75
  }
76
76
 
77
77
  if (attributes.id) {
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'json'
4
+
5
+ require 'bundler/setup' if ENV['CI']
4
6
  require 'haml'
5
7
 
6
8
  class Haml::Parser::ParseNode
@@ -17,6 +17,13 @@ module.exports = {
17
17
  return "";
18
18
  }
19
19
 
20
+ // Here we can skip the entire rest of the method by just checking if it's
21
+ // an args_forward node, as we're guaranteed that there are no other arg
22
+ // nodes.
23
+ if (path.getValue().body[0].type === "args_forward") {
24
+ return "(...)";
25
+ }
26
+
20
27
  const { addTrailingCommas } = opts;
21
28
  const { args, heredocs } = makeArgs(path, opts, print, 0);
22
29
 
@@ -8,7 +8,7 @@ const {
8
8
  removeLines,
9
9
  softline
10
10
  } = require("../prettier");
11
- const { empty, first, hasAncestor } = require("../utils");
11
+ const { empty, hasAncestor } = require("../utils");
12
12
 
13
13
  const printBlock = (path, opts, print) => {
14
14
  const [variables, statements] = path.getValue().body;
@@ -76,6 +76,5 @@ module.exports = {
76
76
  },
77
77
  brace_block: printBlock,
78
78
  do_block: printBlock,
79
- excessed_comma: empty,
80
- number_arg: first
79
+ excessed_comma: empty
81
80
  };
@@ -23,7 +23,7 @@ module.exports = {
23
23
  }
24
24
 
25
25
  return group(
26
- concat([receiver, indent(concat([softline, operator, name]))])
26
+ concat([receiver, group(indent(concat([softline, operator, name])))])
27
27
  );
28
28
  },
29
29
  fcall: concatBody,
@@ -5,7 +5,7 @@ const hasDef = node =>
5
5
  node.body[1].type === "args_add_block" &&
6
6
  node.body[1].body[0].type === "args" &&
7
7
  node.body[1].body[0].body[0] &&
8
- node.body[1].body[0].body[0].type === "def";
8
+ ["def", "defs"].includes(node.body[1].body[0].body[0].type);
9
9
 
10
10
  // Very special handling case for rspec matchers. In general with rspec matchers
11
11
  // you expect to see something like:
@@ -75,20 +75,17 @@ const printTernary = (path, _opts, print) => {
75
75
  );
76
76
  };
77
77
 
78
- const makeSingleBlockForm = (keyword, path, print) =>
79
- concat([
78
+ // Prints an `if_mod` or `unless_mod` node. Because it was previously in the
79
+ // modifier form, we're guaranteed to not have an additional node, so we can
80
+ // just work with the predicate and the body.
81
+ const printSingle = keyword => (path, { inlineConditionals }, print) => {
82
+ const multiline = concat([
80
83
  `${keyword} `,
81
84
  align(keyword.length + 1, path.call(print, "body", 0)),
82
85
  indent(concat([softline, path.call(print, "body", 1)])),
83
86
  concat([softline, "end"])
84
87
  ]);
85
88
 
86
- // Prints an `if_mod` or `unless_mod` node. Because it was previously in the
87
- // modifier form, we're guaranteed to not have an additional node, so we can
88
- // just work with the predicate and the body.
89
- const printSingle = keyword => (path, { inlineConditionals }, print) => {
90
- const multiline = makeSingleBlockForm(keyword, path, print);
91
-
92
89
  const [_predicate, stmts] = path.getValue().body;
93
90
  const hasComments =
94
91
  stmts.type === "stmts" && stmts.body.some(stmt => stmt.type === "@comment");
@@ -230,7 +227,12 @@ const printConditional = keyword => (path, { inlineConditionals }, print) => {
230
227
  // know for sure that it doesn't impact the body of the conditional, so we
231
228
  // have to default to the block form.
232
229
  if (containsAssignment(predicate)) {
233
- return makeSingleBlockForm(keyword, path, print);
230
+ return concat([
231
+ `${keyword} `,
232
+ align(keyword.length + 1, path.call(print, "body", 0)),
233
+ indent(concat([hardline, path.call(print, "body", 1)])),
234
+ concat([hardline, "end"])
235
+ ]);
234
236
  }
235
237
 
236
238
  return printSingle(keyword)(path, { inlineConditionals }, print);
@@ -11,7 +11,7 @@ module.exports = {
11
11
  // If the number is a base 10 number, is sufficiently large, and is not
12
12
  // already formatted with underscores, then add them in in between the
13
13
  // numbers every three characters starting from the right.
14
- if (!body.startsWith("0") && body.length >= 4 && !body.includes("_")) {
14
+ if (!body.startsWith("0") && body.length >= 5 && !body.includes("_")) {
15
15
  return ` ${body}`
16
16
  .slice((body.length + 2) % 3)
17
17
  .match(/.{3}/g)
@@ -10,6 +10,22 @@ const {
10
10
  const { containsAssignment } = require("../utils");
11
11
 
12
12
  const printLoop = (keyword, modifier) => (path, { inlineLoops }, print) => {
13
+ const [_predicate, statements] = path.getValue().body;
14
+
15
+ // If the only statement inside this while loop is a void statement, then we
16
+ // can shorten to just displaying the predicate and then a semicolon.
17
+ if (statements.body.length === 1 && statements.body[0].type === "void_stmt") {
18
+ return group(
19
+ concat([
20
+ keyword,
21
+ " ",
22
+ path.call(print, "body", 0),
23
+ ifBreak(softline, "; "),
24
+ "end"
25
+ ])
26
+ );
27
+ }
28
+
13
29
  let inlineParts = [
14
30
  path.call(print, "body", 1),
15
31
  ` ${keyword} `,
@@ -1,4 +1,5 @@
1
1
  const { concat, group, join, line } = require("../prettier");
2
+ const { literal } = require("../utils");
2
3
 
3
4
  const printGenericRestParam = symbol => (path, opts, print) =>
4
5
  path.getValue().body[0]
@@ -53,7 +54,7 @@ const printParams = (path, opts, print) => {
53
54
  }
54
55
 
55
56
  if (kwargRest) {
56
- parts.push(path.call(print, "body", 5));
57
+ parts.push(kwargRest === "nil" ? "**nil" : path.call(print, "body", 5));
57
58
  }
58
59
 
59
60
  if (block) {
@@ -79,6 +80,7 @@ const paramError = () => {
79
80
  };
80
81
 
81
82
  module.exports = {
83
+ args_forward: literal("..."),
82
84
  kwrest_param: printGenericRestParam("**"),
83
85
  rest_param: printGenericRestParam("*"),
84
86
  params: printParams,
@@ -7,8 +7,8 @@ const {
7
7
  softline,
8
8
  join
9
9
  } = require("../prettier");
10
+
10
11
  const { concatBody, empty, makeList, prefix, surround } = require("../utils");
11
- const escapePattern = require("../escapePattern");
12
12
 
13
13
  // If there is some part of this string that matches an escape sequence or that
14
14
  // contains the interpolation pattern ("#{"), then we are locked into whichever
@@ -19,7 +19,7 @@ const isQuoteLocked = string =>
19
19
  string.body.some(
20
20
  part =>
21
21
  part.type === "@tstring_content" &&
22
- (escapePattern.test(part.body) || part.body.includes("#{"))
22
+ (part.body.includes("#{") || part.body.includes("\\"))
23
23
  );
24
24
 
25
25
  // A string is considered to be able to use single quotes if it contains only
@@ -29,17 +29,9 @@ const isSingleQuotable = string =>
29
29
  part => part.type === "@tstring_content" && !part.body.includes("'")
30
30
  );
31
31
 
32
- const getStringQuote = (string, preferSingleQuotes) => {
33
- if (isQuoteLocked(string)) {
34
- return string.quote;
35
- }
36
-
37
- return preferSingleQuotes && isSingleQuotable(string) ? "'" : '"';
38
- };
39
-
40
32
  const quotePattern = new RegExp("\\\\([\\s\\S])|(['\"])", "g");
41
33
 
42
- const makeString = (content, enclosingQuote, originalQuote) => {
34
+ const normalizeQuotes = (content, enclosingQuote, originalQuote) => {
43
35
  const replaceOther = ["'", '"'].includes(originalQuote);
44
36
  const otherQuote = enclosingQuote === '"' ? "'" : '"';
45
37
 
@@ -62,51 +54,24 @@ const makeString = (content, enclosingQuote, originalQuote) => {
62
54
  });
63
55
  };
64
56
 
65
- // The `parts` argument that comes into this function is an array of either
66
- // printed embedded expressions or plain strings (that themselves can contain
67
- // newlines). What we want to return is an array where each element represents a
68
- // line in the original text. So we end up tracking every line as we go and
69
- // pushing onto a `currentLine` variable, then when we hit a new line we push
70
- // the current line onto the `lines` variable until we've used up every part.
71
- const makeHeredocLines = parts => {
72
- let lines = [];
73
- let currentLine = [];
74
-
75
- parts.forEach(part => {
76
- if (part.type === "group" || !part.includes("\n")) {
77
- // In this case we've either hit an embedded expression or the piece of
78
- // the current line that we're looking at is in the middle of two of them,
79
- // so we just push onto the current line and continue on.
80
- currentLine.push(part);
81
- return;
82
- }
83
-
84
- let splits = part.split("\n");
85
- if (splits[splits.length - 1] === "") {
86
- // If a line ends with a newline, then we end up with an empty string at
87
- // the end of the splits, we just pop it off here since we'll handle the
88
- // newlines later.
89
- splits = splits.slice(0, -1);
90
- }
91
-
92
- if (currentLine.length > 0) {
93
- lines.push(concat(currentLine.concat(splits[0])));
94
- currentLine = [];
95
- } else {
96
- lines.push(splits[0]);
97
- }
57
+ const quotePairs = {
58
+ "(": ")",
59
+ "[": "]",
60
+ "{": "}",
61
+ "<": ">"
62
+ };
98
63
 
99
- if (splits.length > 1) {
100
- lines = lines.concat(splits.slice(1, -1));
101
- currentLine.push(splits[splits.length - 1]);
102
- }
103
- });
64
+ const getClosingQuote = quote => {
65
+ if (!quote.startsWith("%")) {
66
+ return quote;
67
+ }
104
68
 
105
- if (currentLine.length > 0) {
106
- lines = lines.concat(currentLine);
69
+ const boundary = /%q?(.)/.exec(quote)[1];
70
+ if (boundary in quotePairs) {
71
+ return quotePairs[boundary];
107
72
  }
108
73
 
109
- return lines;
74
+ return boundary;
110
75
  };
111
76
 
112
77
  module.exports = {
@@ -121,21 +86,24 @@ module.exports = {
121
86
  return body.length === 2 ? concat([quote, body.slice(1), quote]) : body;
122
87
  },
123
88
  dyna_symbol: (path, opts, print) => {
124
- const { quote } = path.getValue().body[0];
89
+ const { quote } = path.getValue();
125
90
 
126
91
  return concat([":", quote, concat(path.call(print, "body", 0)), quote]);
127
92
  },
128
93
  heredoc: (path, opts, print) => {
129
- const { beging, ending } = path.getValue();
130
- const lines = makeHeredocLines(path.map(print, "body"));
131
-
132
- return concat([
133
- beging,
134
- literalline,
135
- join(literalline, lines),
136
- literalline,
137
- ending
138
- ]);
94
+ const { beging, body, ending } = path.getValue();
95
+
96
+ const parts = body.map((part, index) => {
97
+ if (part.type !== "@tstring_content") {
98
+ // In this case, the part of the string is an embedded expression
99
+ return path.call(print, "body", index);
100
+ }
101
+
102
+ // In this case, the part of the string is just regular string content
103
+ return join(literalline, part.body.split("\n"));
104
+ });
105
+
106
+ return concat([beging, literalline, concat(parts), ending]);
139
107
  },
140
108
  string: makeList,
141
109
  string_concat: (path, opts, print) =>
@@ -162,7 +130,8 @@ module.exports = {
162
130
  );
163
131
  },
164
132
  string_literal: (path, { preferSingleQuotes }, print) => {
165
- const string = path.getValue().body[0];
133
+ const stringLiteral = path.getValue();
134
+ const string = stringLiteral.body[0];
166
135
 
167
136
  // If this string is actually a heredoc, bail out and return to the print
168
137
  // function for heredocs
@@ -176,20 +145,28 @@ module.exports = {
176
145
  return preferSingleQuotes ? "''" : '""';
177
146
  }
178
147
 
179
- const quote = getStringQuote(string, preferSingleQuotes);
180
- const parts = [];
148
+ // Determine the quote that should enclose the new string
149
+ let quote;
150
+ if (isQuoteLocked(string)) {
151
+ ({ quote } = stringLiteral);
152
+ } else {
153
+ quote = preferSingleQuotes && isSingleQuotable(string) ? "'" : '"';
154
+ }
181
155
 
182
- string.body.forEach((part, index) => {
183
- if (part.type === "@tstring_content") {
184
- // In this case, the part of the string is just regular string content
185
- parts.push(makeString(part.body, quote, string.quote));
186
- } else {
156
+ const parts = string.body.map((part, index) => {
157
+ if (part.type !== "@tstring_content") {
187
158
  // In this case, the part of the string is an embedded expression
188
- parts.push(path.call(print, "body", 0, "body", index));
159
+ return path.call(print, "body", 0, "body", index);
189
160
  }
161
+
162
+ // In this case, the part of the string is just regular string content
163
+ return join(
164
+ literalline,
165
+ normalizeQuotes(part.body, quote, stringLiteral.quote).split("\n")
166
+ );
190
167
  });
191
168
 
192
- return concat([quote].concat(parts).concat([quote]));
169
+ return concat([quote].concat(parts).concat(getClosingQuote(quote)));
193
170
  },
194
171
  symbol: prefix(":"),
195
172
  symbol_literal: concatBody,