prettier 1.2.4 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +351 -361
  3. data/README.md +11 -96
  4. data/node_modules/prettier/index.js +54 -54
  5. data/package.json +1 -2
  6. data/src/haml/embed.js +87 -0
  7. data/src/haml/nodes/comment.js +27 -0
  8. data/src/haml/nodes/doctype.js +34 -0
  9. data/src/haml/nodes/filter.js +16 -0
  10. data/src/haml/nodes/hamlComment.js +21 -0
  11. data/src/haml/nodes/plain.js +6 -0
  12. data/src/haml/nodes/root.js +8 -0
  13. data/src/haml/nodes/script.js +33 -0
  14. data/src/haml/nodes/silentScript.js +59 -0
  15. data/src/haml/nodes/tag.js +193 -0
  16. data/src/haml/parser.js +22 -0
  17. data/src/haml/parser.rb +138 -0
  18. data/src/haml/printer.js +28 -0
  19. data/src/parser/getLang.js +32 -0
  20. data/src/parser/getNetcat.js +50 -0
  21. data/src/parser/netcat.js +15 -0
  22. data/src/parser/parseSync.js +33 -0
  23. data/src/parser/requestParse.js +74 -0
  24. data/src/parser/server.rb +61 -0
  25. data/src/plugin.js +26 -4
  26. data/src/rbs/parser.js +39 -0
  27. data/src/rbs/parser.rb +94 -0
  28. data/src/rbs/printer.js +605 -0
  29. data/src/ruby/embed.js +54 -8
  30. data/src/ruby/nodes/args.js +20 -6
  31. data/src/ruby/nodes/arrays.js +36 -33
  32. data/src/ruby/nodes/calls.js +12 -43
  33. data/src/ruby/nodes/class.js +17 -27
  34. data/src/ruby/nodes/commands.js +8 -3
  35. data/src/ruby/nodes/conditionals.js +1 -1
  36. data/src/ruby/nodes/hashes.js +28 -14
  37. data/src/ruby/nodes/loops.js +4 -10
  38. data/src/ruby/nodes/methods.js +4 -11
  39. data/src/ruby/nodes/rescue.js +32 -25
  40. data/src/ruby/nodes/statements.js +6 -5
  41. data/src/ruby/nodes/strings.js +7 -6
  42. data/src/ruby/parser.js +2 -50
  43. data/src/ruby/parser.rb +113 -43
  44. data/src/ruby/printer.js +8 -5
  45. data/src/utils.js +1 -0
  46. data/src/utils/inlineEnsureParens.js +8 -1
  47. data/src/utils/isEmptyBodyStmt.js +7 -0
  48. data/src/utils/isEmptyStmts.js +9 -5
  49. data/src/utils/noIndent.js +1 -0
  50. data/src/utils/printEmptyCollection.js +9 -2
  51. metadata +25 -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: {
@@ -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