prettier 0.17.0 β†’ 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,