prettier 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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
  };