prettier 1.5.1 → 1.6.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -2
  3. data/CONTRIBUTING.md +2 -2
  4. data/README.md +41 -12
  5. data/node_modules/prettier/bin-prettier.js +13702 -11629
  6. data/node_modules/prettier/index.js +19191 -16565
  7. data/node_modules/prettier/parser-angular.js +67 -0
  8. data/node_modules/prettier/parser-babel.js +22 -0
  9. data/node_modules/prettier/parser-espree.js +22 -0
  10. data/node_modules/prettier/parser-flow.js +22 -0
  11. data/node_modules/prettier/parser-glimmer.js +1 -0
  12. data/node_modules/prettier/parser-graphql.js +1 -0
  13. data/node_modules/prettier/parser-html.js +132 -0
  14. data/node_modules/prettier/parser-markdown.js +34 -0
  15. data/node_modules/prettier/parser-meriyah.js +22 -0
  16. data/node_modules/prettier/parser-postcss.js +22 -0
  17. data/node_modules/prettier/parser-typescript.js +22 -0
  18. data/node_modules/prettier/parser-yaml.js +15 -0
  19. data/node_modules/prettier/third-party.js +1042 -833
  20. data/package.json +5 -5
  21. data/rubocop.yml +12 -0
  22. data/src/haml/parser.js +6 -5
  23. data/src/haml/parser.rb +8 -3
  24. data/src/haml/printer.js +428 -18
  25. data/src/parser/netcat.js +0 -2
  26. data/src/parser/parseSync.js +153 -14
  27. data/src/parser/server.rb +7 -2
  28. data/src/plugin.js +7 -1
  29. data/src/rbs/parser.js +3 -5
  30. data/src/rbs/parser.rb +7 -3
  31. data/src/rbs/printer.js +46 -8
  32. data/src/ruby/embed.js +7 -5
  33. data/src/ruby/nodes/args.js +111 -19
  34. data/src/ruby/nodes/calls.js +8 -1
  35. data/src/ruby/nodes/conditionals.js +47 -45
  36. data/src/ruby/nodes/hashes.js +5 -14
  37. data/src/ruby/nodes/heredocs.js +5 -3
  38. data/src/ruby/nodes/params.js +2 -9
  39. data/src/ruby/nodes/strings.js +95 -2
  40. data/src/ruby/parser.js +3 -5
  41. data/src/ruby/parser.rb +82 -31
  42. data/src/ruby/printer.js +10 -1
  43. data/src/utils.js +1 -1
  44. data/src/utils/inlineEnsureParens.js +1 -0
  45. data/src/utils/literallineWithoutBreakParent.js +7 -0
  46. data/src/utils/skipAssignIndent.js +8 -1
  47. metadata +15 -15
  48. data/src/haml/nodes/comment.js +0 -27
  49. data/src/haml/nodes/doctype.js +0 -34
  50. data/src/haml/nodes/filter.js +0 -16
  51. data/src/haml/nodes/hamlComment.js +0 -21
  52. data/src/haml/nodes/plain.js +0 -6
  53. data/src/haml/nodes/root.js +0 -8
  54. data/src/haml/nodes/script.js +0 -33
  55. data/src/haml/nodes/silentScript.js +0 -59
  56. data/src/haml/nodes/tag.js +0 -193
  57. data/src/parser/getLang.js +0 -32
  58. data/src/parser/getNetcat.js +0 -50
  59. data/src/parser/requestParse.js +0 -74
  60. data/src/utils/literalLineNoBreak.js +0 -7
data/src/parser/netcat.js CHANGED
@@ -5,11 +5,9 @@
5
5
  const { createConnection } = require("net");
6
6
 
7
7
  const sock = process.argv[process.argv.length - 1];
8
-
9
8
  const client = createConnection(sock, () => process.stdin.pipe(client));
10
9
 
11
10
  client.on("data", (data) => process.stdout.write(data));
12
-
13
11
  client.on("error", (error) => {
14
12
  console.error(error);
15
13
  });
@@ -1,30 +1,169 @@
1
- const requestParse = require("./requestParse");
1
+ const { spawn, spawnSync, execSync } = require("child_process");
2
+ const { existsSync, mkdtempSync } = require("fs");
3
+ const os = require("os");
4
+ const path = require("path");
5
+ const process = require("process");
6
+
7
+ let sockfile = process.env.PRETTIER_RUBY_HOST;
8
+ let netcat;
9
+
10
+ // In order to properly parse ruby code, we need to tell the ruby process to
11
+ // parse using UTF-8. Unfortunately, the way that you accomplish this looks
12
+ // differently depending on your platform.
13
+ /* istanbul ignore next */
14
+ function getLang() {
15
+ const { env, platform } = process;
16
+ const envValue = env.LC_ALL || env.LC_CTYPE || env.LANG;
17
+
18
+ // If an env var is set for the locale that already includes UTF-8 in the
19
+ // name, then assume we can go with that.
20
+ if (envValue && envValue.includes("UTF-8")) {
21
+ return envValue;
22
+ }
23
+
24
+ // Otherwise, we're going to guess which encoding to use based on the system.
25
+ // This is probably not the best approach in the world, as you could be on
26
+ // linux and not have C.UTF-8, but in that case you're probably passing an env
27
+ // var for it. This object below represents all of the possible values of
28
+ // process.platform per:
29
+ // https://nodejs.org/api/process.html#process_process_platform
30
+ return {
31
+ aix: "C.UTF-8",
32
+ darwin: "en_US.UTF-8",
33
+ freebsd: "C.UTF-8",
34
+ linux: "C.UTF-8",
35
+ openbsd: "C.UTF-8",
36
+ sunos: "C.UTF-8",
37
+ win32: ".UTF-8"
38
+ }[platform];
39
+ }
40
+
41
+ // Spawn the parser.rb subprocess. We do this since booting Ruby is slow, and we
42
+ // can re-use the parser process multiple times since it is statelesss.
43
+ function spawnParseServer() {
44
+ const tmpDir = mkdtempSync(path.join(os.tmpdir(), "prettier-ruby"));
45
+ const tmpFile = path.join(tmpDir, `${process.pid}.sock`);
46
+
47
+ const server = spawn("ruby", [path.join(__dirname, "./server.rb"), tmpFile], {
48
+ env: Object.assign({}, process.env, { LANG: getLang() }),
49
+ detached: true,
50
+ stdio: "inherit"
51
+ });
52
+
53
+ process.on("exit", () => {
54
+ try {
55
+ process.kill(-server.pid);
56
+ } catch (e) {
57
+ // ignore
58
+ }
59
+ });
60
+
61
+ server.unref();
62
+ const now = new Date();
63
+
64
+ // Wait for server to go live.
65
+ while (!existsSync(tmpFile) && new Date() - now < 3000) {
66
+ execSync("sleep 0.1");
67
+ }
68
+
69
+ return tmpFile;
70
+ }
71
+
72
+ // Checks to see if an executable is available.
73
+ function hasCommand(name) {
74
+ let result;
75
+
76
+ if (os.type() === "Windows_NT") {
77
+ result = spawnSync("where", [name]);
78
+ } else {
79
+ result = spawnSync("command", ["-v", name]);
80
+ }
81
+
82
+ return result.status === 0;
83
+ }
84
+
85
+ // Finds a netcat-like adapter to use for sending data to a socket. We order
86
+ // these by likelihood of being found so we can avoid some shell-outs.
87
+ function findNetcat(opts) {
88
+ if (opts.rubyNetcatCommand) {
89
+ const splits = opts.rubyNetcatCommand.split(" ");
90
+ return { command: splits[0], args: splits.slice(1) };
91
+ }
92
+
93
+ if (hasCommand("nc")) {
94
+ return { command: "nc", args: ["-U"] };
95
+ }
96
+
97
+ if (hasCommand("telnet")) {
98
+ return { command: "telnet", args: ["-u"] };
99
+ }
100
+
101
+ if (hasCommand("ncat")) {
102
+ return { command: "ncat", args: ["-U"] };
103
+ }
104
+
105
+ if (hasCommand("socat")) {
106
+ return { command: "socat", args: ["-"] };
107
+ }
108
+
109
+ return { command: "node", args: [require.resolve("./netcat.js")] };
110
+ }
2
111
 
3
112
  // Formats and sends a request to the parser server. We use netcat (or something
4
113
  // like it) here since Prettier requires the results of `parse` to be
5
114
  // synchronous and Node.js does not offer a mechanism for synchronous socket
6
115
  // requests.
7
- function parseSync(parser, source) {
8
- const response = requestParse(parser, source);
116
+ function parseSync(parser, source, opts) {
117
+ if (!sockfile) {
118
+ sockfile = spawnParseServer();
119
+ }
120
+
121
+ if (!netcat) {
122
+ netcat = findNetcat(opts);
123
+ }
9
124
 
125
+ const response = spawnSync(netcat.command, netcat.args.concat(sockfile), {
126
+ input: `${parser}|${source}`,
127
+ maxBuffer: 15 * 1024 * 1024
128
+ });
129
+
130
+ const stdout = response.stdout.toString();
131
+ const stderr = response.stderr.toString();
132
+ const { status } = response;
133
+
134
+ // We need special handling in case the user's version of nc doesn't support
135
+ // using unix sockets.
10
136
  if (
11
- response.stdout.length === 0 ||
12
- (response.status !== null && response.status !== 0)
137
+ stderr.includes("invalid option -- U") ||
138
+ stderr.includes("invalid option -- 'u'") ||
139
+ stderr.includes("Protocol not supported")
13
140
  ) {
14
- console.error("Could not parse response from server");
15
- console.error(response);
141
+ throw new Error(`
142
+ @prettier/plugin-ruby uses unix sockets to communicate between the node.js
143
+ process running prettier and an underlying Ruby process used for parsing.
144
+ Unfortunately the command that it tried to use to do that
145
+ (${netcat.command}) does not support unix sockets. To solve this either
146
+ uninstall the version of ${netcat.command} that you're using and use a
147
+ different implementation, or change the value of the rubyNetcatCommand
148
+ option in your prettier configuration.
149
+ `);
150
+ }
16
151
 
17
- throw new Error(response.stderr || "An unknown error occurred");
152
+ // If we didn't receive anything over stdout or we have a bad exit status,
153
+ // then throw whatever we can.
154
+ if (stdout.length === 0 || (status !== null && status !== 0)) {
155
+ throw new Error(stderr || "An unknown error occurred");
18
156
  }
19
157
 
20
- const parsed = JSON.parse(response.stdout);
158
+ const parsed = JSON.parse(stdout);
21
159
 
22
160
  if (parsed.error) {
23
- throw new Error(
24
- `@prettier/plugin-ruby encountered an error when attempting to parse \
25
- the ruby source. This usually means there was a syntax error in the \
26
- file in question. You can verify by running \`ruby -i [path/to/file]\`.`
27
- );
161
+ const error = new Error(parsed.error);
162
+ if (parsed.loc) {
163
+ error.loc = parsed.loc;
164
+ }
165
+
166
+ throw error;
28
167
  }
29
168
 
30
169
  return parsed;
data/src/parser/server.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/setup' if ENV['CI']
3
+ require 'bundler/setup' if ENV['PLUGIN_RUBY_CI']
4
4
  require 'socket'
5
5
  require 'json'
6
6
 
@@ -14,7 +14,7 @@ $PROGRAM_NAME = 'prettier-ruby-parser'
14
14
  # Make sure we trap these signals to be sure we get the quit command coming from
15
15
  # the parent node process
16
16
  quit = false
17
- trap(:QUIT) { quit = true }
17
+ trap(:QUIT) { quit = true } if RUBY_PLATFORM != 'java'
18
18
  trap(:INT) { quit = true }
19
19
  trap(:TERM) { quit = true }
20
20
 
@@ -48,6 +48,11 @@ loop do
48
48
  else
49
49
  socket.write('{ "error": true }')
50
50
  end
51
+ rescue Prettier::Parser::ParserError => error
52
+ loc = { start: { line: error.lineno, column: error.column } }
53
+ socket.write(JSON.fast_generate(error: error.message, loc: loc))
54
+ rescue StandardError => error
55
+ socket.write(JSON.fast_generate(error: error.message))
51
56
  ensure
52
57
  socket.close
53
58
  end
data/src/plugin.js CHANGED
@@ -10,7 +10,7 @@ const hamlParser = require("./haml/parser");
10
10
  /*
11
11
  * metadata mostly pulled from linguist and rubocop:
12
12
  * https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
13
- * https://github.com/rubocop-hq/rubocop/blob/master/spec/rubocop/target_finder_spec.rb
13
+ * https://github.com/rubocop/rubocop/blob/master/spec/rubocop/target_finder_spec.rb
14
14
  */
15
15
 
16
16
  module.exports = {
@@ -119,6 +119,12 @@ module.exports = {
119
119
  description:
120
120
  "When it fits on one line, allows if, unless, while, and until statements to use the modifier form."
121
121
  },
122
+ rubyNetcatCommand: {
123
+ type: "string",
124
+ category: "Ruby",
125
+ description:
126
+ 'The prefix of the command to execute to communicate between the node.js process and the Ruby process. (For example, "nc -U" or "telnet -u") Normally you should not set this option.'
127
+ },
122
128
  rubySingleQuote: {
123
129
  type: "boolean",
124
130
  category: "Ruby",
data/src/rbs/parser.js CHANGED
@@ -4,16 +4,14 @@ const parseSync = require("../parser/parseSync");
4
4
  // to prettier a JavaScript object that is the equivalent AST that represents
5
5
  // the code stored in that string. We accomplish this by spawning a new Ruby
6
6
  // process of parser.rb and reading JSON off STDOUT.
7
- function parse(text, _parsers, _opts) {
8
- return parseSync("rbs", text);
7
+ function parse(text, _parsers, opts) {
8
+ return parseSync("rbs", text, opts);
9
9
  }
10
10
 
11
- const pragmaPattern = /#\s*@(prettier|format)/;
12
-
13
11
  // This function handles checking whether or not the source string has the
14
12
  // pragma for prettier. This is an optional workflow for incremental adoption.
15
13
  function hasPragma(text) {
16
- return pragmaPattern.test(text);
14
+ return /^\s*#[^\S\n]*@(format|prettier)\s*(\n|$)/.test(text);
17
15
  }
18
16
 
19
17
  // This function is critical for comments and cursor support, and is responsible
data/src/rbs/parser.rb CHANGED
@@ -86,9 +86,13 @@ end
86
86
  module Prettier
87
87
  class RBSParser
88
88
  def self.parse(text)
89
- { declarations: RBS::Parser.parse_signature(text) }
90
- rescue StandardError
91
- false
89
+ {
90
+ declarations: RBS::Parser.parse_signature(text),
91
+ location: {
92
+ start_pos: 0,
93
+ end_pos: text.length
94
+ }
95
+ }
92
96
  end
93
97
  end
94
98
  end
data/src/rbs/printer.js CHANGED
@@ -147,7 +147,7 @@ function printNode(path, opts, print) {
147
147
 
148
148
  // This is the big function that prints out any individual type, which can
149
149
  // look like all kinds of things, listed in the case statement below.
150
- function printType(path) {
150
+ function printType(path, { forceParens = false } = {}) {
151
151
  const node = path.getValue();
152
152
 
153
153
  switch (node.class) {
@@ -157,7 +157,13 @@ function printNode(path, opts, print) {
157
157
  }
158
158
  return node.literal;
159
159
  case "optional":
160
- return concat([path.call(printType, "type"), "?"]);
160
+ return concat([
161
+ path.call(
162
+ (typePath) => printType(typePath, { forceParens: true }),
163
+ "type"
164
+ ),
165
+ "?"
166
+ ]);
161
167
  case "tuple":
162
168
  // If we don't have any sub types, we explicitly need the space in between
163
169
  // the brackets to not confuse the parser.
@@ -173,11 +179,29 @@ function printNode(path, opts, print) {
173
179
  join(concat([line, "| "]), path.map(printType, "types"))
174
180
  );
175
181
 
176
- const parentType = path.getParentNode().class;
177
- return parentType === "intersection" ? concat(["(", doc, ")"]) : doc;
182
+ if (forceParens) {
183
+ return concat(["(", doc, ")"]);
184
+ }
185
+
186
+ return doc;
187
+ }
188
+ case "intersection": {
189
+ const doc = group(
190
+ join(
191
+ concat([line, "& "]),
192
+ path.map(
193
+ (typePath) => printType(typePath, { forceParens: true }),
194
+ "types"
195
+ )
196
+ )
197
+ );
198
+
199
+ if (forceParens) {
200
+ return concat(["(", doc, ")"]);
201
+ }
202
+
203
+ return doc;
178
204
  }
179
- case "intersection":
180
- return group(join(concat([line, "& "]), path.map(printType, "types")));
181
205
  case "class_singleton":
182
206
  return concat(["singleton(", node.name, ")"]);
183
207
  case "proc":
@@ -515,7 +539,14 @@ function printNode(path, opts, print) {
515
539
  );
516
540
  }
517
541
 
518
- parts.push("-> ", path.call(printType, "type", "return_type"));
542
+ parts.push(
543
+ "-> ",
544
+ path.call(
545
+ (typePath) => printType(typePath, { forceParens: true }),
546
+ "type",
547
+ "return_type"
548
+ )
549
+ );
519
550
 
520
551
  return group(concat(parts));
521
552
  }
@@ -599,7 +630,14 @@ function hasPrettierIgnore(path) {
599
630
  return node.comment && node.comment.string.includes("prettier-ignore");
600
631
  }
601
632
 
633
+ // This function handles adding the format pragma to a source string. This is an
634
+ // optional workflow for incremental adoption.
635
+ function insertPragma(text) {
636
+ return `# @format\n${text}`;
637
+ }
638
+
602
639
  module.exports = {
603
640
  print: printNode,
604
- hasPrettierIgnore
641
+ hasPrettierIgnore,
642
+ insertPragma
605
643
  };
data/src/ruby/embed.js CHANGED
@@ -8,7 +8,7 @@ const {
8
8
  stripTrailingHardline
9
9
  } = require("../prettier");
10
10
 
11
- const { literalLineNoBreak } = require("../utils");
11
+ const { literallineWithoutBreakParent } = require("../utils");
12
12
 
13
13
  const parsers = {
14
14
  css: "css",
@@ -30,7 +30,7 @@ function replaceNewlines(doc) {
30
30
  ? concat(
31
31
  currentDoc
32
32
  .split(/(\n)/g)
33
- .map((v, i) => (i % 2 === 0 ? v : literalLineNoBreak))
33
+ .map((v, i) => (i % 2 === 0 ? v : literallineWithoutBreakParent))
34
34
  )
35
35
  : currentDoc
36
36
  );
@@ -106,7 +106,7 @@ function embed(path, print, textToDoc, _opts) {
106
106
 
107
107
  // Pass that content into the embedded parser. Get back the doc node.
108
108
  const formatted = concat([
109
- literalLineNoBreak,
109
+ literallineWithoutBreakParent,
110
110
  replaceNewlines(stripTrailingHardline(textToDoc(content, { parser })))
111
111
  ]);
112
112
 
@@ -119,7 +119,7 @@ function embed(path, print, textToDoc, _opts) {
119
119
  group(
120
120
  concat([
121
121
  indent(markAsRoot(formatted)),
122
- literalLineNoBreak,
122
+ literallineWithoutBreakParent,
123
123
  ending.trim()
124
124
  ])
125
125
  )
@@ -132,7 +132,9 @@ function embed(path, print, textToDoc, _opts) {
132
132
  return markAsRoot(
133
133
  concat([
134
134
  path.call(print, "beging"),
135
- lineSuffix(group(concat([formatted, literalLineNoBreak, ending.trim()])))
135
+ lineSuffix(
136
+ group(concat([formatted, literallineWithoutBreakParent, ending.trim()]))
137
+ )
136
138
  ])
137
139
  );
138
140
  }
@@ -58,7 +58,7 @@ function printArgParen(path, opts, print) {
58
58
  concat([
59
59
  softline,
60
60
  join(concat([",", line]), path.call(print, "body", 0)),
61
- getTrailingComma(opts) && getArgParenTrailingComma(argsNode)
61
+ getTrailingComma(opts) ? getArgParenTrailingComma(argsNode) : ""
62
62
  ])
63
63
  ),
64
64
  softline,
@@ -106,25 +106,117 @@ function printArgs(path, { rubyToProc }, print) {
106
106
  return args;
107
107
  }
108
108
 
109
- module.exports = {
110
- arg_paren: printArgParen,
111
- args: printArgs,
112
- args_add_block: (path, opts, print) => {
113
- const parts = path.call(print, "body", 0);
109
+ function printArgsAddBlock(path, opts, print) {
110
+ const node = path.getValue();
111
+ const blockNode = node.body[1];
112
+
113
+ const parts = path.call(print, "body", 0);
114
+
115
+ if (blockNode) {
116
+ let blockDoc = path.call(print, "body", 1);
117
+
118
+ if (!(blockNode.comments || []).some(({ leading }) => leading)) {
119
+ // If we don't have any leading comments, we can just prepend the
120
+ // operator.
121
+ blockDoc = concat(["&", blockDoc]);
122
+ } else if (Array.isArray(blockDoc[0])) {
123
+ // If we have a method call like:
124
+ //
125
+ // foo(
126
+ // # comment
127
+ // &block
128
+ // )
129
+ //
130
+ // then we need to make sure we don't accidentally prepend the operator
131
+ // before the comment.
132
+ //
133
+ // In prettier >= 2.3.0, the comments are printed as an array before the
134
+ // content. I don't love this kind of reflection, but it's the simplest
135
+ // way at the moment to get this right.
136
+ blockDoc = blockDoc[0].concat(
137
+ concat(["&", blockDoc[1]]),
138
+ blockDoc.slice(2)
139
+ );
140
+ } else {
141
+ // In prettier < 2.3.0, the comments are printed as part of a concat, so
142
+ // we can reflect on how many leading comments there are to determine
143
+ // which doc node we should modify.
144
+ const index = blockNode.comments.filter(({ leading }) => leading).length;
145
+ blockDoc.parts[index] = concat(["&", blockDoc.parts[index]]);
146
+ }
147
+
148
+ parts.push(blockDoc);
149
+ }
150
+
151
+ return parts;
152
+ }
153
+
154
+ function printArgsAddStar(path, opts, print) {
155
+ let docs = [];
156
+
157
+ path.each((argPath, argIndex) => {
158
+ const doc = print(argPath);
159
+
160
+ // If it's the first child, then it's an array of args, so we're going to
161
+ // concat that onto the existing docs if there are any.
162
+ if (argIndex === 0) {
163
+ if (doc.length > 0) {
164
+ docs = docs.concat(doc);
165
+ }
166
+ return;
167
+ }
168
+
169
+ // If it's after the splat, then it's an individual arg, so we're just going
170
+ // to push it onto the array.
171
+ if (argIndex !== 1) {
172
+ docs.push(doc);
173
+ return;
174
+ }
114
175
 
115
- if (path.getValue().body[1]) {
116
- parts.push(concat(["&", path.call(print, "body", 1)]));
176
+ // If we don't have any leading comments, we can just prepend the operator.
177
+ const argsNode = argPath.getValue();
178
+ if (!(argsNode.comments || []).some(({ leading }) => leading)) {
179
+ docs.push(concat(["*", doc]));
180
+ return;
117
181
  }
118
182
 
119
- return parts;
120
- },
121
- args_add_star: (path, opts, print) => {
122
- const printed = path.map(print, "body");
123
- const parts = printed[0]
124
- .concat([concat(["*", printed[1]])])
125
- .concat(printed.slice(2));
126
-
127
- return parts;
128
- },
129
- blockarg: (path, opts, print) => concat(["&", path.call(print, "body", 0)])
183
+ // If we have an array like:
184
+ //
185
+ // [
186
+ // # comment
187
+ // *values
188
+ // ]
189
+ //
190
+ // then we need to make sure we don't accidentally prepend the operator
191
+ // before the comment(s).
192
+ //
193
+ // In prettier >= 2.3.0, the comments are printed as an array before the
194
+ // content. I don't love this kind of reflection, but it's the simplest way
195
+ // at the moment to get this right.
196
+ if (Array.isArray(doc[0])) {
197
+ docs.push(doc[0].concat(concat(["*", doc[1]]), doc.slice(2)));
198
+ return;
199
+ }
200
+
201
+ // In prettier < 2.3.0, the comments are printed as part of a concat, so
202
+ // we can reflect on how many leading comments there are to determine which
203
+ // doc node we should modify.
204
+ const index = argsNode.comments.filter(({ leading }) => leading).length;
205
+ doc.parts[index] = concat(["*", doc.parts[index]]);
206
+ docs = docs.concat(doc);
207
+ }, "body");
208
+
209
+ return docs;
210
+ }
211
+
212
+ function printBlockArg(path, opts, print) {
213
+ return concat(["&", path.call(print, "body", 0)]);
214
+ }
215
+
216
+ module.exports = {
217
+ arg_paren: printArgParen,
218
+ args: printArgs,
219
+ args_add_block: printArgsAddBlock,
220
+ args_add_star: printArgsAddStar,
221
+ blockarg: printBlockArg
130
222
  };