prettier 1.2.3 → 1.5.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +349 -358
  3. data/README.md +21 -93
  4. data/node_modules/prettier/index.js +54 -54
  5. data/package.json +1 -2
  6. data/rubocop.yml +26 -0
  7. data/src/haml/embed.js +87 -0
  8. data/src/haml/nodes/comment.js +27 -0
  9. data/src/haml/nodes/doctype.js +34 -0
  10. data/src/haml/nodes/filter.js +16 -0
  11. data/src/haml/nodes/hamlComment.js +21 -0
  12. data/src/haml/nodes/plain.js +6 -0
  13. data/src/haml/nodes/root.js +8 -0
  14. data/src/haml/nodes/script.js +33 -0
  15. data/src/haml/nodes/silentScript.js +59 -0
  16. data/src/haml/nodes/tag.js +193 -0
  17. data/src/haml/parser.js +22 -0
  18. data/src/haml/parser.rb +138 -0
  19. data/src/haml/printer.js +28 -0
  20. data/src/parser/getLang.js +32 -0
  21. data/src/parser/getNetcat.js +50 -0
  22. data/src/parser/netcat.js +15 -0
  23. data/src/parser/parseSync.js +33 -0
  24. data/src/parser/requestParse.js +74 -0
  25. data/src/parser/server.rb +61 -0
  26. data/src/plugin.js +26 -4
  27. data/src/prettier.js +1 -0
  28. data/src/rbs/parser.js +39 -0
  29. data/src/rbs/parser.rb +94 -0
  30. data/src/rbs/printer.js +605 -0
  31. data/src/ruby/embed.js +58 -8
  32. data/src/ruby/nodes/args.js +20 -6
  33. data/src/ruby/nodes/blocks.js +64 -59
  34. data/src/ruby/nodes/calls.js +12 -43
  35. data/src/ruby/nodes/class.js +17 -27
  36. data/src/ruby/nodes/commands.js +7 -2
  37. data/src/ruby/nodes/conditionals.js +1 -1
  38. data/src/ruby/nodes/hashes.js +28 -14
  39. data/src/ruby/nodes/hooks.js +9 -19
  40. data/src/ruby/nodes/loops.js +4 -10
  41. data/src/ruby/nodes/methods.js +8 -17
  42. data/src/ruby/nodes/params.js +22 -14
  43. data/src/ruby/nodes/patterns.js +9 -5
  44. data/src/ruby/nodes/rescue.js +32 -25
  45. data/src/ruby/nodes/return.js +0 -4
  46. data/src/ruby/nodes/statements.js +11 -13
  47. data/src/ruby/nodes/strings.js +27 -35
  48. data/src/ruby/parser.js +2 -49
  49. data/src/ruby/parser.rb +256 -232
  50. data/src/ruby/printer.js +0 -2
  51. data/src/ruby/toProc.js +4 -8
  52. data/src/utils.js +1 -0
  53. data/src/utils/isEmptyBodyStmt.js +7 -0
  54. data/src/utils/isEmptyStmts.js +9 -5
  55. data/src/utils/makeCall.js +3 -0
  56. data/src/utils/noIndent.js +1 -0
  57. data/src/utils/printEmptyCollection.js +9 -2
  58. metadata +26 -2
@@ -0,0 +1,28 @@
1
+ const embed = require("./embed");
2
+ const nodes = {
3
+ comment: require("./nodes/comment"),
4
+ doctype: require("./nodes/doctype"),
5
+ filter: require("./nodes/filter"),
6
+ haml_comment: require("./nodes/hamlComment"),
7
+ plain: require("./nodes/plain"),
8
+ root: require("./nodes/root"),
9
+ script: require("./nodes/script"),
10
+ silent_script: require("./nodes/silentScript"),
11
+ tag: require("./nodes/tag")
12
+ };
13
+
14
+ const genericPrint = (path, opts, print) => {
15
+ const { type } = path.getValue();
16
+
17
+ /* istanbul ignore next */
18
+ if (!(type in nodes)) {
19
+ throw new Error(`Unsupported node encountered: ${type}`);
20
+ }
21
+
22
+ return nodes[type](path, opts, print);
23
+ };
24
+
25
+ module.exports = {
26
+ embed,
27
+ print: genericPrint
28
+ };
@@ -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,5 +1,11 @@
1
- const printer = require("./ruby/printer");
2
- const parser = require("./ruby/parser");
1
+ const rubyPrinter = require("./ruby/printer");
2
+ const rubyParser = require("./ruby/parser");
3
+
4
+ const rbsPrinter = require("./rbs/printer");
5
+ const rbsParser = require("./rbs/parser");
6
+
7
+ const hamlPrinter = require("./haml/printer");
8
+ const hamlParser = require("./haml/parser");
3
9
 
4
10
  /*
5
11
  * metadata mostly pulled from linguist and rubocop:
@@ -30,6 +36,7 @@ module.exports = {
30
36
  ".rabl",
31
37
  ".rake",
32
38
  ".rb",
39
+ ".rbi",
33
40
  ".rbuild",
34
41
  ".rbw",
35
42
  ".rbx",
@@ -67,13 +74,28 @@ module.exports = {
67
74
  interpreters: ["jruby", "macruby", "rake", "rbx", "ruby"],
68
75
  linguistLanguageId: 326,
69
76
  vscodeLanguageIds: ["ruby"]
77
+ },
78
+ {
79
+ name: "RBS",
80
+ parsers: ["rbs"],
81
+ extensions: [".rbs"]
82
+ },
83
+ {
84
+ name: "HAML",
85
+ parsers: ["haml"],
86
+ extensions: [".haml"],
87
+ vscodeLanguageIds: ["haml"]
70
88
  }
71
89
  ],
72
90
  parsers: {
73
- ruby: parser
91
+ ruby: rubyParser,
92
+ rbs: rbsParser,
93
+ haml: hamlParser
74
94
  },
75
95
  printers: {
76
- ruby: printer
96
+ ruby: rubyPrinter,
97
+ rbs: rbsPrinter,
98
+ haml: hamlPrinter
77
99
  },
78
100
  options: {
79
101
  rubyArrayLiteral: {
@@ -1,6 +1,7 @@
1
1
  // If `RBPRETTIER` is set, then this is being run from the `Prettier::run` ruby
2
2
  // method. In that case, we need to pull `prettier` from the node_modules
3
3
  // directly, as it's been shipped with the gem.
4
+ /* istanbul ignore next */
4
5
  const source = process.env.RBPRETTIER ? "../node_modules/prettier" : "prettier";
5
6
 
6
7
  const prettier = require(source);
@@ -0,0 +1,39 @@
1
+ const parseSync = require("../parser/parseSync");
2
+
3
+ // This function is responsible for taking an input string of text and returning
4
+ // to prettier a JavaScript object that is the equivalent AST that represents
5
+ // the code stored in that string. We accomplish this by spawning a new Ruby
6
+ // process of parser.rb and reading JSON off STDOUT.
7
+ function parse(text, _parsers, _opts) {
8
+ return parseSync("rbs", text);
9
+ }
10
+
11
+ const pragmaPattern = /#\s*@(prettier|format)/;
12
+
13
+ // This function handles checking whether or not the source string has the
14
+ // pragma for prettier. This is an optional workflow for incremental adoption.
15
+ function hasPragma(text) {
16
+ return pragmaPattern.test(text);
17
+ }
18
+
19
+ // This function is critical for comments and cursor support, and is responsible
20
+ // for returning the index of the character within the source string that is the
21
+ // beginning of the given node.
22
+ function locStart(node) {
23
+ return (node.location || node.type.location).start_pos;
24
+ }
25
+
26
+ // This function is critical for comments and cursor support, and is responsible
27
+ // for returning the index of the character within the source string that is the
28
+ // ending of the given node.
29
+ function locEnd(node) {
30
+ return (node.location || node.type.location).end_pos;
31
+ }
32
+
33
+ module.exports = {
34
+ parse,
35
+ astFormat: "rbs",
36
+ hasPragma,
37
+ locStart,
38
+ locEnd
39
+ };
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env ruby
2
+
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
21
+
22
+ # Monkey-patch this so that we can get the character positions.
23
+ class RBS::Location
24
+ def to_json(*args)
25
+ {
26
+ start: {
27
+ line: start_line,
28
+ column: start_column
29
+ },
30
+ end: {
31
+ line: end_line,
32
+ column: end_column
33
+ },
34
+ start_pos: start_pos,
35
+ end_pos: end_pos
36
+ }.to_json(*args)
37
+ end
38
+ end
39
+
40
+ # Monkey-patch this so that we get whether or not it needs to be escaped.
41
+ class RBS::Types::Function::Param
42
+ def to_json(*a)
43
+ escaped = name && /\A#{RBS::Parser::KEYWORDS_RE}\z/.match?(name)
44
+ { type: type, name: name, escaped: escaped }.to_json(*a)
45
+ end
46
+ end
47
+
48
+ # Monkey-patch this so that we get the name field in the serialized JSON, as
49
+ # well as information about whether or not we need to escape it.
50
+ class RBS::AST::Members::MethodDefinition
51
+ def to_json(*a)
52
+ {
53
+ member: :method_definition,
54
+ name: name,
55
+ kind: kind,
56
+ types: types,
57
+ annotations: annotations,
58
+ location: location,
59
+ comment: comment,
60
+ overload: overload
61
+ }.to_json(*a)
62
+ end
63
+ end
64
+
65
+ # Monkey-patch this so that we get the information we need about how to join the
66
+ # key-value pairs of the record.
67
+ class RBS::Types::Record
68
+ def to_json(*a)
69
+ fields_extra = {}
70
+
71
+ # Explicitly not using Enumerable#to_h here to support Ruby 2.5
72
+ fields.each do |key, type|
73
+ if key.is_a?(Symbol) && key.match?(/\A[A-Za-z_][A-Za-z_]*\z/) &&
74
+ !key.match?(RBS::Parser::KEYWORDS_RE)
75
+ fields_extra[key] = { type: type, joiner: :label }
76
+ else
77
+ fields_extra[key.inspect] = { type: type, joiner: :rocket }
78
+ end
79
+ end
80
+
81
+ { class: :record, fields: fields_extra, location: location }.to_json(*a)
82
+ end
83
+ end
84
+
85
+ # The main parser interface.
86
+ module Prettier
87
+ class RBSParser
88
+ def self.parse(text)
89
+ { declarations: RBS::Parser.parse_signature(text) }
90
+ rescue StandardError
91
+ false
92
+ end
93
+ end
94
+ end