prettier 1.3.0 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
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:
data/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@prettier/plugin-ruby",
3
- "version": "1.3.0",
3
+ "version": "1.5.3",
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",
@@ -22,9 +22,9 @@
22
22
  "prettier": ">=1.10"
23
23
  },
24
24
  "devDependencies": {
25
- "eslint": "^7.8.1",
26
- "eslint-config-prettier": "^7.0.0",
27
- "husky": "^4.3.5",
25
+ "eslint": "^7.21.0",
26
+ "eslint-config-prettier": "^8.0.0",
27
+ "husky": "^5.0.9",
28
28
  "jest": "^26.0.0",
29
29
  "pretty-quick": "^3.1.0"
30
30
  },
data/rubocop.yml CHANGED
@@ -24,3 +24,6 @@ Style/TrailingCommaInArrayLiteral: # trailingComma
24
24
 
25
25
  Style/TrailingCommaInHashLiteral: # trailingComma
26
26
  Enabled: false
27
+
28
+ Style/Lambda:
29
+ Enabled: false
data/src/haml/parser.js CHANGED
@@ -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)/;
data/src/haml/parser.rb CHANGED
@@ -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)
@@ -115,27 +131,6 @@ module Prettier
115
131
  class HAMLParser
116
132
  def self.parse(source)
117
133
  Haml::Parser.new({}).call(source).as_json
118
- rescue StandardError
119
- false
120
134
  end
121
135
  end
122
136
  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,28 @@
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 { stdout, stderr, status } = requestParse(parser, source);
9
+
10
+ if (stdout.length === 0 || (status !== null && status !== 0)) {
11
+ throw new Error(stderr || "An unknown error occurred");
12
+ }
13
+
14
+ const parsed = JSON.parse(stdout);
15
+
16
+ if (parsed.error) {
17
+ const error = new Error(parsed.error);
18
+ if (parsed.loc) {
19
+ error.loc = parsed.loc;
20
+ }
21
+
22
+ throw error;
23
+ }
24
+
25
+ return parsed;
26
+ }
27
+
28
+ 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,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup' if ENV['PLUGIN_RUBY_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 } if RUBY_PLATFORM != 'java'
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
+ 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))
56
+ ensure
57
+ socket.close
58
+ end
59
+ rescue IO::WaitReadable, Errno::EINTR
60
+ # Wait for select(2) to give us a connection that has content for 1 second.
61
+ # Otherwise timeout and continue on (so that we hit our "break if quit"
62
+ # pretty often).
63
+ IO.select([server], nil, nil, 1)
64
+
65
+ retry unless quit
66
+ end
data/src/plugin.js CHANGED
@@ -36,6 +36,7 @@ module.exports = {
36
36
  ".rabl",
37
37
  ".rake",
38
38
  ".rb",
39
+ ".rbi",
39
40
  ".rbuild",
40
41
  ".rbw",
41
42
  ".rbx",
data/src/rbs/parser.js CHANGED
@@ -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)/;
data/src/rbs/parser.rb CHANGED
@@ -1,21 +1,43 @@
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
9
24
  def to_json(*args)
10
25
  {
11
- start: { line: start_line, column: start_column },
12
- end: { line: end_line, column: end_column },
26
+ start: {
27
+ line: start_line,
28
+ column: start_column
29
+ },
30
+ end: {
31
+ line: end_line,
32
+ column: end_column
33
+ },
13
34
  start_pos: start_pos,
14
35
  end_pos: end_pos
15
36
  }.to_json(*args)
16
37
  end
17
38
  end
18
39
 
40
+ # Monkey-patch this so that we get whether or not it needs to be escaped.
19
41
  class RBS::Types::Function::Param
20
42
  def to_json(*a)
21
43
  escaped = name && /\A#{RBS::Parser::KEYWORDS_RE}\z/.match?(name)
@@ -60,32 +82,11 @@ class RBS::Types::Record
60
82
  end
61
83
  end
62
84
 
85
+ # The main parser interface.
63
86
  module Prettier
64
87
  class RBSParser
65
88
  def self.parse(text)
66
89
  { declarations: RBS::Parser.parse_signature(text) }
67
- rescue StandardError
68
- false
69
90
  end
70
91
  end
71
92
  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