prettier 1.3.0 → 1.5.3

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.
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