prettier 1.3.0 → 1.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 657db7d1eef6e33d72337eb6b666678675c9eced4cc19716466b4bd74c039cfc
4
- data.tar.gz: d69ffa4e3b2b48146c112a9ceb0d4ddab3351119c1bb2b9d92848f7beb3f40e4
3
+ metadata.gz: b87bc10882cc30779fa7d83d4b52bada919561ed298c0a42331f6df5b8681fb4
4
+ data.tar.gz: fc46f3778f9824d8a9b5f2d15e70a6419e01ed921b3ed3c7f4a0ff4699b7aa2a
5
5
  SHA512:
6
- metadata.gz: d7c4d27daddee1e5d07393c5358e48a419c75a6e1aff62b36779d093f16f3da613c938b98c188752c93835e66ddd9578ebf6595fcde1a1f69f40f2613b492ddd
7
- data.tar.gz: c586e33dcd0057779689df71ab68d8858aac27c9e89cf2f500edd2b55779b26693cf6756815cc616054c364b6bc4e1adff05263036f74025425218c04676969c
6
+ metadata.gz: 76a79ab1910791821427f896140c008a3b5430a99236c65225b24c633bd333462ad77fd5f90c6f9de060568809aa05585198aba8fcfe64f1c30c381c9c6f8ce7
7
+ data.tar.gz: 408d7cabd27ce93415ee1cf5248746c62fbba32ab3cab820fbdc02f45da2ae2555a89eca2d3be81573acfa02d331a06628a332e22021e4771d51dd2c2a51adf4
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.4.0] - 2021-01-15
10
+
11
+ ### Added
12
+
13
+ - [@ianks], [@kddeisz] - Use `netcat` to communicate to a parser server for better performance when formatting multiple files.
14
+
15
+ ### Changed
16
+
17
+ - [@jeffcarbs], [@kddeisz] - Long strings with interpolated expressions should only break if the contents in the original source is broken.
18
+ - [@johannesluedke], [@kddeisz] - Fix for rescues with inline comments.
19
+ - [@johncsnyder], [@kddeisz] - Comments should not attach to nodes beyond a double newline.
20
+ - [@blampe], [@kddeisz] - Comments inside of a broken method chain get dropped.
21
+ - [@clarkdave], [@kddeisz] - Don't ignore Sorbet `sig` transformations.
22
+
9
23
  ## [1.3.0] - 2021-01-05
10
24
 
11
25
  ### Added
@@ -1035,7 +1049,8 @@ would previously result in `array[]`, but now prints properly.
1035
1049
 
1036
1050
  - Initial release 🎉
1037
1051
 
1038
- [unreleased]: https://github.com/prettier/plugin-ruby/compare/v1.3.0...HEAD
1052
+ [unreleased]: https://github.com/prettier/plugin-ruby/compare/v1.4.0...HEAD
1053
+ [1.4.0]: https://github.com/prettier/plugin-ruby/compare/v1.3.0...v1.4.0
1039
1054
  [1.3.0]: https://github.com/prettier/plugin-ruby/compare/v1.2.5...v1.3.0
1040
1055
  [1.2.5]: https://github.com/prettier/plugin-ruby/compare/v1.2.4...v1.2.5
1041
1056
  [1.2.4]: https://github.com/prettier/plugin-ruby/compare/v1.2.3...v1.2.4
@@ -1107,6 +1122,7 @@ would previously result in `array[]`, but now prints properly.
1107
1122
  [@bugthing]: https://github.com/bugthing
1108
1123
  [@cbothner]: https://github.com/cbothner
1109
1124
  [@christoomey]: https://github.com/christoomey
1125
+ [@clarkdave]: https://github.com/clarkdave
1110
1126
  [@cldevs]: https://github.com/cldevs
1111
1127
  [@codingitwrong]: https://github.com/CodingItWrong
1112
1128
  [@coiti]: https://github.com/coiti
@@ -1126,7 +1142,9 @@ would previously result in `array[]`, but now prints properly.
1126
1142
  [@jamescostian]: https://github.com/jamescostian
1127
1143
  [@janklimo]: https://github.com/janklimo
1128
1144
  [@jbielick]: https://github.com/jbielick
1145
+ [@jeffcarbs]: https://github.com/jeffcarbs
1129
1146
  [@joeyjoejoejr]: https://github.com/joeyjoejoejr
1147
+ [@johannesluedke]: https://github.com/johannesluedke
1130
1148
  [@johncsnyder]: https://github.com/johncsnyder
1131
1149
  [@johnschoeman]: https://github.com/johnschoeman
1132
1150
  [@joshuakgoldberg]: https://github.com/JoshuaKGoldberg
data/README.md CHANGED
@@ -138,7 +138,7 @@ Below are the options (from [`src/plugin.js`](src/plugin.js)) that `@prettier/pl
138
138
  | `rubySingleQuote` | `--ruby-single-quote` | `true` | When double quotes are not necessary for interpolation, prefers the use of single quotes for string literals. |
139
139
  | `rubyToProc` | `--ruby-to-proc` | `false` | When possible, convert blocks to the more concise `Symbol#to_proc` syntax. |
140
140
  | `tabWidth` | `--tab-width` | `2` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tab-width)). |
141
- | `trailingComma` | `--trailing-comma` | `"es5"` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#trailing-comma)). `"es5"` is equivalent to `true`. |
141
+ | `trailingComma` | `--trailing-comma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#trailing-comma)). `"es5"` is equivalent to `true`. |
142
142
 
143
143
  Any of these can be added to your existing [prettier configuration
144
144
  file](https://prettier.io/docs/en/configuration.html). For example:
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@prettier/plugin-ruby",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "prettier plugin for the Ruby programming language",
5
5
  "main": "src/plugin.js",
6
6
  "scripts": {
7
7
  "check-format": "prettier --check '**/*'",
8
8
  "lint": "eslint --cache .",
9
- "test": "PORT=$(bin/port) jest"
9
+ "test": "jest"
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
@@ -1,18 +1,7 @@
1
- const { spawnSync } = require("child_process");
2
- const path = require("path");
3
-
4
- const parser = path.join(__dirname, "./parser.rb");
1
+ const parseSync = require("../parser/parseSync");
5
2
 
6
3
  const parse = (text, _parsers, _opts) => {
7
- const child = spawnSync("ruby", [parser], { input: text });
8
-
9
- const error = child.stderr.toString();
10
- if (error) {
11
- throw new Error(error);
12
- }
13
-
14
- const response = child.stdout.toString();
15
- return JSON.parse(response);
4
+ return parseSync("haml", text);
16
5
  };
17
6
 
18
7
  const pragmaPattern = /^\s*-#\s*@(prettier|format)/;
@@ -1,10 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/setup' if ENV['CI']
4
- require 'haml'
5
- require 'json'
6
3
  require 'ripper'
7
4
 
5
+ begin
6
+ require 'haml'
7
+ rescue LoadError
8
+ # If we can't load the haml gem, then we're going to provide a shim parser
9
+ # that will warn and bail out.
10
+ class Prettier::HAMLParser
11
+ def self.parse(text)
12
+ warn(
13
+ 'The `haml` gem could not be loaded. Please ensure you have it ' \
14
+ 'installed and that it is available in the gem path.'
15
+ )
16
+
17
+ false
18
+ end
19
+ end
20
+
21
+ return
22
+ end
23
+
8
24
  class Haml::Parser::ParseNode
9
25
  class DeepAttributeParser
10
26
  def parse(string)
@@ -120,22 +136,3 @@ module Prettier
120
136
  end
121
137
  end
122
138
  end
123
-
124
- # If this is the main file we're executing, then most likely this is being
125
- # executed from the haml.js spawn. In that case, read the ruby source from
126
- # stdin and report back the AST over stdout.
127
- if $0 == __FILE__
128
- response = Prettier::HAMLParser.parse($stdin.read)
129
-
130
- if !response
131
- warn(
132
- '@prettier/plugin-ruby encountered an error when attempting to parse ' \
133
- 'the HAML source. This usually means there was a syntax error in the ' \
134
- 'file in question.'
135
- )
136
-
137
- exit 1
138
- end
139
-
140
- puts JSON.fast_generate(response)
141
- end
@@ -0,0 +1,32 @@
1
+ // In order to properly parse ruby code, we need to tell the ruby process to
2
+ // parse using UTF-8. Unfortunately, the way that you accomplish this looks
3
+ // differently depending on your platform.
4
+ /* istanbul ignore next */
5
+ function getLang() {
6
+ const { env, platform } = process;
7
+ const envValue = env.LC_ALL || env.LC_CTYPE || env.LANG;
8
+
9
+ // If an env var is set for the locale that already includes UTF-8 in the
10
+ // name, then assume we can go with that.
11
+ if (envValue && envValue.includes("UTF-8")) {
12
+ return envValue;
13
+ }
14
+
15
+ // Otherwise, we're going to guess which encoding to use based on the system.
16
+ // This is probably not the best approach in the world, as you could be on
17
+ // linux and not have C.UTF-8, but in that case you're probably passing an env
18
+ // var for it. This object below represents all of the possible values of
19
+ // process.platform per:
20
+ // https://nodejs.org/api/process.html#process_process_platform
21
+ return {
22
+ aix: "C.UTF-8",
23
+ darwin: "en_US.UTF-8",
24
+ freebsd: "C.UTF-8",
25
+ linux: "C.UTF-8",
26
+ openbsd: "C.UTF-8",
27
+ sunos: "C.UTF-8",
28
+ win32: ".UTF-8"
29
+ }[platform];
30
+ }
31
+
32
+ module.exports = getLang;
@@ -0,0 +1,50 @@
1
+ const { spawnSync } = require("child_process");
2
+ const os = require("os");
3
+
4
+ // Checks to see if an executable is available.
5
+ function hasCommand(name) {
6
+ let result;
7
+
8
+ if (os.type() === "Windows_NT") {
9
+ result = spawnSync("where", [name]);
10
+ } else {
11
+ result = spawnSync("command", ["-v", name]);
12
+ }
13
+
14
+ return result.status === 0;
15
+ }
16
+
17
+ // Finds an netcat-like adapter to use for sending data to a socket. We order
18
+ // these by likelihood of being found so we can avoid some shell-outs.
19
+ function getCommandAndArg() {
20
+ if (hasCommand("nc")) {
21
+ return ["nc", "-U"];
22
+ }
23
+
24
+ if (hasCommand("telnet")) {
25
+ return ["telnet", "-u"];
26
+ }
27
+
28
+ if (hasCommand("ncat")) {
29
+ return ["ncat", "-U"];
30
+ }
31
+
32
+ if (hasCommand("socat")) {
33
+ return ["socat", "-"];
34
+ }
35
+
36
+ return ["node", require.resolve("./netcat.js")];
37
+ }
38
+
39
+ let command;
40
+ let arg;
41
+
42
+ function getNetcat() {
43
+ if (!command) {
44
+ [command, arg] = getCommandAndArg();
45
+ }
46
+
47
+ return { command, arg };
48
+ }
49
+
50
+ module.exports = getNetcat;
@@ -0,0 +1,15 @@
1
+ // A simple fallback when no netcat-compatible adapter is found on the system.
2
+ // On average, this is 2-3x slower than netcat, but still much faster than
3
+ // spawning a new Ruby process.
4
+
5
+ const { createConnection } = require("net");
6
+
7
+ const sock = process.argv[process.argv.length - 1];
8
+
9
+ const client = createConnection(sock, () => process.stdin.pipe(client));
10
+
11
+ client.on("data", (data) => process.stdout.write(data));
12
+
13
+ client.on("error", (error) => {
14
+ console.error(error);
15
+ });
@@ -0,0 +1,33 @@
1
+ const requestParse = require("./requestParse");
2
+
3
+ // Formats and sends a request to the parser server. We use netcat (or something
4
+ // like it) here since Prettier requires the results of `parse` to be
5
+ // synchronous and Node.js does not offer a mechanism for synchronous socket
6
+ // requests.
7
+ function parseSync(parser, source) {
8
+ const response = requestParse(parser, source);
9
+
10
+ if (
11
+ response.stdout.length === 0 ||
12
+ (response.status !== null && response.status !== 0)
13
+ ) {
14
+ console.error("Could not parse response from server");
15
+ console.error(response);
16
+
17
+ throw new Error(response.stderr || "An unknown error occurred");
18
+ }
19
+
20
+ const parsed = JSON.parse(response.stdout);
21
+
22
+ 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
+ );
28
+ }
29
+
30
+ return parsed;
31
+ }
32
+
33
+ module.exports = parseSync;
@@ -0,0 +1,74 @@
1
+ const { spawn, spawnSync, execSync } = require("child_process");
2
+ const path = require("path");
3
+ const { existsSync, mkdtempSync } = require("fs");
4
+ const process = require("process");
5
+ const os = require("os");
6
+
7
+ const getNetcat = require("./getNetcat");
8
+ const getLang = require("./getLang");
9
+
10
+ let sockfile = process.env.PRETTIER_RUBY_HOST;
11
+
12
+ // Spawn the parser.rb subprocess. We do this since booting Ruby is slow, and we
13
+ // can re-use the parser process multiple times since it is statelesss.
14
+ function spawnParseServer() {
15
+ const server = spawn(
16
+ "ruby",
17
+ [path.join(__dirname, "./server.rb"), sockfile],
18
+ {
19
+ env: Object.assign({}, process.env, { LANG: getLang() }),
20
+ detached: true,
21
+ stdio: "inherit"
22
+ }
23
+ );
24
+
25
+ process.on("exit", () => {
26
+ try {
27
+ process.kill(-server.pid);
28
+ } catch (e) {
29
+ // ignore
30
+ }
31
+ });
32
+
33
+ server.unref();
34
+ const now = new Date();
35
+
36
+ // Wait for server to go live.
37
+ while (!existsSync(sockfile) && new Date() - now < 3000) {
38
+ execSync("sleep 0.1");
39
+ }
40
+ }
41
+
42
+ // Ensures that a parser server is currently running by checking against the
43
+ // sockfile variable. If it is not, create a temporary directory to house the
44
+ // sockfile and spawn the ruby process.
45
+ function ensureParseServer() {
46
+ if (sockfile) {
47
+ return;
48
+ }
49
+
50
+ const tmpDir = mkdtempSync(path.join(os.tmpdir(), "prettier-ruby"));
51
+ sockfile = path.join(tmpDir, `${process.pid}.sock`);
52
+
53
+ spawnParseServer();
54
+ }
55
+
56
+ // Sends a request to the parse server to parse the given content.
57
+ function requestParse(parser, source) {
58
+ ensureParseServer();
59
+
60
+ const { command, arg } = getNetcat();
61
+ const { stdout, stderr, status } = spawnSync(command, [arg, sockfile], {
62
+ input: `${parser}|${source}`,
63
+ maxBuffer: 15 * 1024 * 1024
64
+ });
65
+
66
+ return {
67
+ command,
68
+ stdout: stdout.toString(),
69
+ stderr: stderr.toString(),
70
+ status
71
+ };
72
+ }
73
+
74
+ module.exports = requestParse;
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup' if ENV['CI']
4
+ require 'socket'
5
+ require 'json'
6
+
7
+ require_relative '../ruby/parser'
8
+ require_relative '../rbs/parser'
9
+ require_relative '../haml/parser'
10
+
11
+ # Set the program name so that it's easy to find if we need it
12
+ $PROGRAM_NAME = 'prettier-ruby-parser'
13
+
14
+ # Make sure we trap these signals to be sure we get the quit command coming from
15
+ # the parent node process
16
+ quit = false
17
+ trap(:QUIT) { quit = true }
18
+ trap(:INT) { quit = true }
19
+ trap(:TERM) { quit = true }
20
+
21
+ sockfile = ARGV.first || "/tmp/#{$PROGRAM_NAME}.sock"
22
+ server = UNIXServer.new(sockfile)
23
+
24
+ at_exit do
25
+ server.close
26
+ File.unlink(sockfile)
27
+ end
28
+
29
+ loop do
30
+ break if quit
31
+
32
+ # Start up a new thread that will handle each successive connection.
33
+ Thread.new(server.accept_nonblock) do |socket|
34
+ parser, source = socket.read.force_encoding('UTF-8').split('|', 2)
35
+
36
+ response =
37
+ case parser
38
+ when 'ruby'
39
+ Prettier::Parser.parse(source)
40
+ when 'rbs'
41
+ Prettier::RBSParser.parse(source)
42
+ when 'haml'
43
+ Prettier::HAMLParser.parse(source)
44
+ end
45
+
46
+ if response
47
+ socket.write(JSON.fast_generate(response))
48
+ else
49
+ socket.write('{ "error": true }')
50
+ end
51
+ ensure
52
+ socket.close
53
+ end
54
+ rescue IO::WaitReadable, Errno::EINTR
55
+ # Wait for select(2) to give us a connection that has content for 1 second.
56
+ # Otherwise timeout and continue on (so that we hit our "break if quit"
57
+ # pretty often).
58
+ IO.select([server], nil, nil, 1)
59
+
60
+ retry unless quit
61
+ end
@@ -1,23 +1,11 @@
1
- const { spawnSync } = require("child_process");
2
- const path = require("path");
1
+ const parseSync = require("../parser/parseSync");
3
2
 
4
3
  // This function is responsible for taking an input string of text and returning
5
4
  // to prettier a JavaScript object that is the equivalent AST that represents
6
5
  // the code stored in that string. We accomplish this by spawning a new Ruby
7
6
  // process of parser.rb and reading JSON off STDOUT.
8
7
  function parse(text, _parsers, _opts) {
9
- const child = spawnSync("ruby", [path.join(__dirname, "./parser.rb")], {
10
- input: text,
11
- maxBuffer: 15 * 1024 * 1024 // 15MB
12
- });
13
-
14
- const error = child.stderr.toString();
15
- if (error) {
16
- throw new Error(error);
17
- }
18
-
19
- const response = child.stdout.toString();
20
- return JSON.parse(response);
8
+ return parseSync("rbs", text);
21
9
  }
22
10
 
23
11
  const pragmaPattern = /#\s*@(prettier|format)/;
@@ -1,8 +1,23 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'bundler/setup' if ENV['CI']
4
- require 'json'
5
- require 'rbs'
3
+ begin
4
+ require 'rbs'
5
+ rescue LoadError
6
+ # If we can't load the rbs gem, then we're going to provide a shim parser that
7
+ # will warn and bail out.
8
+ class Prettier::RBSParser
9
+ def self.parse(text)
10
+ warn(
11
+ 'The `rbs` gem could not be loaded. Please ensure you have it ' \
12
+ 'installed and that it is available in the gem path.'
13
+ )
14
+
15
+ false
16
+ end
17
+ end
18
+
19
+ return
20
+ end
6
21
 
7
22
  # Monkey-patch this so that we can get the character positions.
8
23
  class RBS::Location
@@ -16,6 +31,7 @@ class RBS::Location
16
31
  end
17
32
  end
18
33
 
34
+ # Monkey-patch this so that we get whether or not it needs to be escaped.
19
35
  class RBS::Types::Function::Param
20
36
  def to_json(*a)
21
37
  escaped = name && /\A#{RBS::Parser::KEYWORDS_RE}\z/.match?(name)
@@ -60,6 +76,7 @@ class RBS::Types::Record
60
76
  end
61
77
  end
62
78
 
79
+ # The main parser interface.
63
80
  module Prettier
64
81
  class RBSParser
65
82
  def self.parse(text)
@@ -69,23 +86,3 @@ module Prettier
69
86
  end
70
87
  end
71
88
  end
72
-
73
- # If this is the main file we're executing, then most likely this is being
74
- # executed from the parser.js spawn. In that case, read the rbs source from
75
- # stdin and report back the AST over stdout.
76
-
77
- if $0 == __FILE__
78
- response = Prettier::RBSParser.parse($stdin.read)
79
-
80
- if !response
81
- warn(
82
- '@prettier/plugin-ruby encountered an error when attempting to parse ' \
83
- 'the RBS source. This usually means there was a syntax error in the ' \
84
- 'file in question.'
85
- )
86
-
87
- exit 1
88
- end
89
-
90
- puts JSON.fast_generate(response)
91
- end
@@ -43,7 +43,7 @@ function printCall(path, opts, print) {
43
43
 
44
44
  // If our parent node is a chained node then we're not going to group the
45
45
  // right side of the expression, as we want to have a nice multi-line layout.
46
- if (chained.includes(parentNode.type)) {
46
+ if (chained.includes(parentNode.type) && !node.comments) {
47
47
  parentNode.chain = (node.chain || 0) + 1;
48
48
  parentNode.callChain = (node.callChain || 0) + 1;
49
49
  parentNode.breakDoc = (node.breakDoc || [receiverDoc]).concat(rightSideDoc);
@@ -103,7 +103,7 @@ function printMethodAddArg(path, opts, print) {
103
103
 
104
104
  // If our parent node is a chained node then we're not going to group the
105
105
  // right side of the expression, as we want to have a nice multi-line layout.
106
- if (chained.includes(parentNode.type)) {
106
+ if (chained.includes(parentNode.type) && !node.comments) {
107
107
  parentNode.chain = (node.chain || 0) + 1;
108
108
  parentNode.breakDoc = (node.breakDoc || [methodDoc]).concat(argsDoc);
109
109
  parentNode.firstReceiverType = node.firstReceiverType;
@@ -136,41 +136,12 @@ function printMethodAddArg(path, opts, print) {
136
136
  return concat([methodDoc, argsDoc]);
137
137
  }
138
138
 
139
- // Sorbet type annotations look like the following:
140
- //
141
- // {method_add_block
142
- // [{method_add_arg
143
- // [{fcall
144
- // [{@ident "sig"}]},
145
- // {args []}]},
146
- // {brace_block [nil, {stmts}]}}]}
147
- //
148
- function isSorbetTypeAnnotation(node) {
149
- const [callNode, blockNode] = node.body;
150
-
151
- return (
152
- callNode.type === "method_add_arg" &&
153
- callNode.body[0].type === "fcall" &&
154
- callNode.body[0].body[0].body === "sig" &&
155
- callNode.body[1].type === "args" &&
156
- callNode.body[1].body.length === 0 &&
157
- blockNode
158
- );
159
- }
160
-
161
139
  function printMethodAddBlock(path, opts, print) {
162
140
  const node = path.getValue();
163
141
 
164
142
  const [callNode, blockNode] = node.body;
165
143
  const [callDoc, blockDoc] = path.map(print, "body");
166
144
 
167
- // Very special handling here for sorbet type annotations. They look like Ruby
168
- // code, but they're not actually Ruby code, so we're not going to mess with
169
- // them at all.
170
- if (isSorbetTypeAnnotation(node)) {
171
- return opts.originalText.slice(opts.locStart(node), opts.locEnd(node));
172
- }
173
-
174
145
  // Don't bother trying to do any kind of fancy toProc transform if the option
175
146
  // is disabled.
176
147
  if (opts.rubyToProc) {
@@ -26,28 +26,10 @@ function printEnsure(path, opts, print) {
26
26
  }
27
27
 
28
28
  function printRescue(path, opts, print) {
29
- const [exception, variable, _stmts, addition] = path.getValue().body;
30
29
  const parts = ["rescue"];
31
30
 
32
- if (exception || variable) {
33
- if (exception) {
34
- if (Array.isArray(exception)) {
35
- // In this case, it's actually only the one exception (it's an array
36
- // of length 1).
37
- parts.push(" ", path.call(print, "body", 0, 0));
38
- } else {
39
- // Here we have multiple exceptions from which we're rescuing, so we
40
- // need to align them and join them together.
41
- const joiner = concat([",", line]);
42
- const exceptions = group(join(joiner, path.call(print, "body", 0)));
43
-
44
- parts.push(" ", align("rescue ".length, exceptions));
45
- }
46
- }
47
-
48
- if (variable) {
49
- parts.push(" => ", path.call(print, "body", 1));
50
- }
31
+ if (path.getValue().body[0]) {
32
+ parts.push(align("rescue ".length, path.call(print, "body", 0)));
51
33
  } else {
52
34
  // If you don't specify an error to rescue in a `begin/rescue` block, then
53
35
  // implicitly you're rescuing from `StandardError`. In this case, we're
@@ -55,16 +37,40 @@ function printRescue(path, opts, print) {
55
37
  parts.push(" StandardError");
56
38
  }
57
39
 
58
- const rescueBody = path.call(print, "body", 2);
40
+ const bodystmt = path.call(print, "body", 1);
59
41
 
60
- if (rescueBody.parts.length > 0) {
61
- parts.push(indent(concat([hardline, rescueBody])));
42
+ if (bodystmt.parts.length > 0) {
43
+ parts.push(indent(concat([hardline, bodystmt])));
62
44
  }
63
45
 
64
46
  // This is the next clause on the `begin` statement, either another
65
47
  // `rescue`, and `ensure`, or an `else` clause.
66
- if (addition) {
67
- parts.push(concat([hardline, path.call(print, "body", 3)]));
48
+ if (path.getValue().body[2]) {
49
+ parts.push(concat([hardline, path.call(print, "body", 2)]));
50
+ }
51
+
52
+ return group(concat(parts));
53
+ }
54
+
55
+ // This is a container node that we're adding into the AST that isn't present in
56
+ // Ripper solely so that we have a nice place to attach inline comments.
57
+ function printRescueEx(path, opts, print) {
58
+ const [exception, variable] = path.getValue().body;
59
+ const parts = [];
60
+
61
+ if (exception) {
62
+ let exceptionDoc = path.call(print, "body", 0);
63
+
64
+ if (Array.isArray(exceptionDoc)) {
65
+ const joiner = concat([",", line]);
66
+ exceptionDoc = group(join(joiner, exceptionDoc));
67
+ }
68
+
69
+ parts.push(" ", exceptionDoc);
70
+ }
71
+
72
+ if (variable) {
73
+ parts.push(" => ", path.call(print, "body", 1));
68
74
  }
69
75
 
70
76
  return group(concat(parts));
@@ -89,6 +95,7 @@ module.exports = {
89
95
  ensure: printEnsure,
90
96
  redo: literal("redo"),
91
97
  rescue: printRescue,
98
+ rescue_ex: printRescueEx,
92
99
  rescue_mod: printRescueMod,
93
100
  retry: literal("retry")
94
101
  };
@@ -79,6 +79,9 @@ module.exports = {
79
79
  const { body } = path.getValue();
80
80
  return concat([trim, "__END__", literalline, body]);
81
81
  },
82
+ "@comment"(path, opts, _print) {
83
+ return opts.printer.printComment(path);
84
+ },
82
85
  bodystmt: printBodyStmt,
83
86
  paren: printParen,
84
87
  program: (path, opts, print) =>
@@ -4,6 +4,7 @@ const {
4
4
  hardline,
5
5
  indent,
6
6
  literalline,
7
+ removeLines,
7
8
  softline,
8
9
  join
9
10
  } = require("../../prettier");
@@ -103,14 +104,14 @@ function printStringDVar(path, opts, print) {
103
104
  }
104
105
 
105
106
  function printStringEmbExpr(path, opts, print) {
107
+ const node = path.getValue();
106
108
  const parts = path.call(print, "body", 0);
107
109
 
108
- // If the interpolated expression is inside of a heredoc or an xstring
109
- // literal (a string that gets sent to the command line) then we don't want
110
- // to automatically indent, as this can lead to some very odd looking
111
- // expressions
112
- if (["heredoc", "xstring_literal"].includes(path.getParentNode().type)) {
113
- return concat(["#{", parts, "}"]);
110
+ // If the contents of this embedded expression were originally on the same
111
+ // line in the source, then we're going to leave them in place and assume
112
+ // that's the way the developer wanted this expression represented.
113
+ if (node.sl === node.el) {
114
+ return concat(["#{", removeLines(parts), "}"]);
114
115
  }
115
116
 
116
117
  return group(
@@ -1,59 +1,11 @@
1
- const { spawnSync } = require("child_process");
2
- const path = require("path");
3
-
4
- // In order to properly parse ruby code, we need to tell the ruby process to
5
- // parse using UTF-8. Unfortunately, the way that you accomplish this looks
6
- // differently depending on your platform.
7
- /* istanbul ignore next */
8
- const LANG = (() => {
9
- const { env, platform } = process;
10
- const envValue = env.LC_ALL || env.LC_CTYPE || env.LANG;
11
-
12
- // If an env var is set for the locale that already includes UTF-8 in the
13
- // name, then assume we can go with that.
14
- if (envValue && envValue.includes("UTF-8")) {
15
- return envValue;
16
- }
17
-
18
- // Otherwise, we're going to guess which encoding to use based on the system.
19
- // This is probably not the best approach in the world, as you could be on
20
- // linux and not have C.UTF-8, but in that case you're probably passing an env
21
- // var for it. This object below represents all of the possible values of
22
- // process.platform per:
23
- // https://nodejs.org/api/process.html#process_process_platform
24
- return {
25
- aix: "C.UTF-8",
26
- darwin: "en_US.UTF-8",
27
- freebsd: "C.UTF-8",
28
- linux: "C.UTF-8",
29
- openbsd: "C.UTF-8",
30
- sunos: "C.UTF-8",
31
- win32: ".UTF-8"
32
- }[platform];
33
- })();
1
+ const parseSync = require("../parser/parseSync");
34
2
 
35
3
  // This function is responsible for taking an input string of text and returning
36
4
  // to prettier a JavaScript object that is the equivalent AST that represents
37
5
  // the code stored in that string. We accomplish this by spawning a new Ruby
38
6
  // process of parser.rb and reading JSON off STDOUT.
39
7
  function parse(text, _parsers, _opts) {
40
- const child = spawnSync(
41
- "ruby",
42
- ["--disable-gems", path.join(__dirname, "./parser.rb")],
43
- {
44
- env: Object.assign({}, process.env, { LANG }),
45
- input: text,
46
- maxBuffer: 15 * 1024 * 1024 // 15MB
47
- }
48
- );
49
-
50
- const error = child.stderr.toString();
51
- if (error) {
52
- throw new Error(error);
53
- }
54
-
55
- const response = child.stdout.toString();
56
- return JSON.parse(response);
8
+ return parseSync("ruby", text);
57
9
  }
58
10
 
59
11
  const pragmaPattern = /#\s*@(prettier|format)/;
@@ -22,6 +22,10 @@ module Prettier; end
22
22
  class Prettier::Parser < Ripper
23
23
  attr_reader :source, :lines, :scanner_events, :line_counts
24
24
 
25
+ # This is an attr_accessor so Stmts objects can grab comments out of this
26
+ # array and attach them to themselves.
27
+ attr_accessor :comments
28
+
25
29
  def initialize(source, *args)
26
30
  super(source, *args)
27
31
 
@@ -125,6 +129,7 @@ class Prettier::Parser < Ripper
125
129
  @comments << {
126
130
  type: :@comment,
127
131
  value: value[1..-1].chomp.force_encoding('UTF-8'),
132
+ inline: value.strip != lines[lineno - 1],
128
133
  sl: lineno,
129
134
  el: lineno,
130
135
  sc: char_pos,
@@ -1768,8 +1773,8 @@ class Prettier::Parser < Ripper
1768
1773
  def bind_end(ec)
1769
1774
  merge!(ec: ec)
1770
1775
 
1771
- stmts = self[:body][2]
1772
- consequent = self[:body][3]
1776
+ stmts = self[:body][1]
1777
+ consequent = self[:body][2]
1773
1778
 
1774
1779
  if consequent
1775
1780
  consequent.bind_end(ec)
@@ -1784,16 +1789,30 @@ class Prettier::Parser < Ripper
1784
1789
  # inside of a bodystmt.
1785
1790
  def on_rescue(exceptions, variable, stmts, consequent)
1786
1791
  beging = find_scanner_event(:@kw, 'rescue')
1792
+ exceptions = exceptions[0] if exceptions.is_a?(Array)
1787
1793
 
1788
- last_exception = exceptions.is_a?(Array) ? exceptions[-1] : exceptions
1789
- last_node = variable || last_exception || beging
1790
-
1794
+ last_node = variable || exceptions || beging
1791
1795
  stmts.bind(find_next_statement_start(last_node[:ec]), char_pos)
1792
1796
 
1797
+ # We add an additional inner node here that ripper doesn't provide so that
1798
+ # we have a nice place to attach inline comment. But we only need it if we
1799
+ # have an exception or a variable that we're rescuing.
1800
+ rescue_ex =
1801
+ if exceptions || variable
1802
+ {
1803
+ type: :rescue_ex,
1804
+ body: [exceptions, variable],
1805
+ sl: beging[:sl],
1806
+ sc: beging[:ec] + 1,
1807
+ el: last_node[:el],
1808
+ ec: last_node[:ec]
1809
+ }
1810
+ end
1811
+
1793
1812
  Rescue.new(
1794
1813
  beging.merge!(
1795
1814
  type: :rescue,
1796
- body: [exceptions, variable, stmts, consequent],
1815
+ body: [rescue_ex, stmts, consequent],
1797
1816
  el: lineno,
1798
1817
  ec: char_pos
1799
1818
  )
@@ -1892,12 +1911,21 @@ class Prettier::Parser < Ripper
1892
1911
  # propagate that onto void_stmt nodes inside the stmts in order to make sure
1893
1912
  # all comments get printed appropriately.
1894
1913
  class Stmts < SimpleDelegator
1914
+ attr_reader :parser
1915
+
1916
+ def initialize(parser, values)
1917
+ @parser = parser
1918
+ __setobj__(values)
1919
+ end
1920
+
1895
1921
  def bind(sc, ec)
1896
1922
  merge!(sc: sc, ec: ec)
1897
1923
 
1898
1924
  if self[:body][0][:type] == :void_stmt
1899
1925
  self[:body][0].merge!(sc: sc, ec: sc)
1900
1926
  end
1927
+
1928
+ attach_comments(sc, ec)
1901
1929
  end
1902
1930
 
1903
1931
  def bind_end(ec)
@@ -1914,6 +1942,22 @@ class Prettier::Parser < Ripper
1914
1942
  self[:body] << statement
1915
1943
  self
1916
1944
  end
1945
+
1946
+ private
1947
+
1948
+ def attach_comments(sc, ec)
1949
+ attachable =
1950
+ parser.comments.select do |comment|
1951
+ comment[:type] == :@comment && !comment[:inline] &&
1952
+ sc <= comment[:sc] && ec >= comment[:ec] &&
1953
+ !comment[:value].include?('prettier-ignore')
1954
+ end
1955
+
1956
+ return if attachable.empty?
1957
+
1958
+ parser.comments -= attachable
1959
+ self[:body] = (self[:body] + attachable).sort_by! { |node| node[:sc] }
1960
+ end
1917
1961
  end
1918
1962
 
1919
1963
  # stmts_new is a parser event that represents the beginning of a list of
@@ -1921,6 +1965,7 @@ class Prettier::Parser < Ripper
1921
1965
  # stmts_add events, which we'll append onto an array body.
1922
1966
  def on_stmts_new
1923
1967
  Stmts.new(
1968
+ self,
1924
1969
  type: :stmts,
1925
1970
  body: [],
1926
1971
  sl: lineno,
@@ -2549,23 +2594,3 @@ class Prettier::Parser < Ripper
2549
2594
  find_scanner_event(:@kw, 'super').merge!(type: :zsuper)
2550
2595
  end
2551
2596
  end
2552
-
2553
- # If this is the main file we're executing, then most likely this is being
2554
- # executed from the parser.js spawn. In that case, read the ruby source from
2555
- # stdin and report back the AST over stdout.
2556
-
2557
- if $0 == __FILE__
2558
- response = Prettier::Parser.parse($stdin.read)
2559
-
2560
- if !response
2561
- warn(
2562
- '@prettier/plugin-ruby encountered an error when attempting to parse ' \
2563
- 'the ruby source. This usually means there was a syntax error in the ' \
2564
- 'file in question. You can verify by running `ruby -i [path/to/file]`.'
2565
- )
2566
-
2567
- exit 1
2568
- end
2569
-
2570
- puts JSON.fast_generate(response)
2571
- end
@@ -54,8 +54,6 @@ function getCommentChildNodes(node) {
54
54
  switch (node.type) {
55
55
  case "heredoc":
56
56
  return [node.beging];
57
- case "rescue":
58
- return [].concat(node.body[0]).concat(node.body.slice(1));
59
57
  case "aryptn":
60
58
  return [node.body[0]]
61
59
  .concat(node.body[1])
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prettier
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Deisz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-05 00:00:00.000000000 Z
11
+ date: 2021-01-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -44,6 +44,12 @@ files:
44
44
  - src/haml/parser.js
45
45
  - src/haml/parser.rb
46
46
  - src/haml/printer.js
47
+ - src/parser/getLang.js
48
+ - src/parser/getNetcat.js
49
+ - src/parser/netcat.js
50
+ - src/parser/parseSync.js
51
+ - src/parser/requestParse.js
52
+ - src/parser/server.rb
47
53
  - src/plugin.js
48
54
  - src/prettier.js
49
55
  - src/rbs/parser.js
@@ -117,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
123
  - !ruby/object:Gem::Version
118
124
  version: '0'
119
125
  requirements: []
120
- rubygems_version: 3.0.3
126
+ rubygems_version: 3.1.4
121
127
  signing_key:
122
128
  specification_version: 4
123
129
  summary: prettier plugin for the Ruby programming language