prettier 1.2.5 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +353 -367
  3. data/README.md +10 -4
  4. data/package.json +1 -1
  5. data/src/haml/embed.js +87 -0
  6. data/src/haml/nodes/comment.js +27 -0
  7. data/src/haml/nodes/doctype.js +34 -0
  8. data/src/haml/nodes/filter.js +16 -0
  9. data/src/haml/nodes/hamlComment.js +21 -0
  10. data/src/haml/nodes/plain.js +6 -0
  11. data/src/haml/nodes/root.js +8 -0
  12. data/src/haml/nodes/script.js +33 -0
  13. data/src/haml/nodes/silentScript.js +59 -0
  14. data/src/haml/nodes/tag.js +193 -0
  15. data/src/haml/parser.js +22 -0
  16. data/src/haml/parser.rb +138 -0
  17. data/src/haml/printer.js +28 -0
  18. data/src/parser/getLang.js +32 -0
  19. data/src/parser/getNetcat.js +50 -0
  20. data/src/parser/netcat.js +15 -0
  21. data/src/parser/parseSync.js +33 -0
  22. data/src/parser/requestParse.js +74 -0
  23. data/src/parser/server.rb +61 -0
  24. data/src/plugin.js +26 -4
  25. data/src/rbs/parser.js +39 -0
  26. data/src/rbs/parser.rb +94 -0
  27. data/src/rbs/printer.js +605 -0
  28. data/src/ruby/embed.js +61 -13
  29. data/src/ruby/nodes/args.js +20 -6
  30. data/src/ruby/nodes/arrays.js +36 -33
  31. data/src/ruby/nodes/calls.js +2 -31
  32. data/src/ruby/nodes/class.js +17 -27
  33. data/src/ruby/nodes/commands.js +1 -1
  34. data/src/ruby/nodes/conditionals.js +1 -1
  35. data/src/ruby/nodes/hashes.js +28 -14
  36. data/src/ruby/nodes/heredocs.js +5 -3
  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 +118 -33
  44. data/src/ruby/printer.js +8 -5
  45. data/src/utils.js +2 -1
  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/literallineWithoutBreakParent.js +7 -0
  50. data/src/utils/printEmptyCollection.js +9 -2
  51. metadata +26 -3
  52. data/src/utils/literalLineNoBreak.js +0 -7
@@ -0,0 +1,22 @@
1
+ const parseSync = require("../parser/parseSync");
2
+
3
+ const parse = (text, _parsers, _opts) => {
4
+ return parseSync("haml", text);
5
+ };
6
+
7
+ const pragmaPattern = /^\s*-#\s*@(prettier|format)/;
8
+ const hasPragma = (text) => pragmaPattern.test(text);
9
+
10
+ // These functions are just placeholders until we can actually perform this
11
+ // properly. The functions are necessary otherwise the format with cursor
12
+ // functions break.
13
+ const locStart = (_node) => 0;
14
+ const locEnd = (_node) => 0;
15
+
16
+ module.exports = {
17
+ parse,
18
+ astFormat: "haml",
19
+ hasPragma,
20
+ locStart,
21
+ locEnd
22
+ };
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ripper'
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
+
24
+ class Haml::Parser::ParseNode
25
+ class DeepAttributeParser
26
+ def parse(string)
27
+ Haml::AttributeParser.available? ? parse_value(string) : string
28
+ end
29
+
30
+ private
31
+
32
+ def literal(string, level)
33
+ level == 0 ? string : "&#{string}"
34
+ end
35
+
36
+ def parse_value(string, level = 0)
37
+ response = Ripper.sexp(string)
38
+ return literal(string, level) unless response
39
+
40
+ case response[1][0][0]
41
+ when :hash
42
+ hash = Haml::AttributeParser.parse(string)
43
+
44
+ if hash
45
+ # Explicitly not using Enumerable#to_h here to support Ruby 2.5
46
+ hash.each_with_object({}) do |(key, value), response|
47
+ response[key] = parse_value(value, level + 1)
48
+ end
49
+ else
50
+ literal(string, level)
51
+ end
52
+ when :string_literal
53
+ string[1...-1]
54
+ else
55
+ literal(string, level)
56
+ end
57
+ end
58
+ end
59
+
60
+ ESCAPE = /Haml::Helpers.html_escape\(\((.+)\)\)/.freeze
61
+
62
+ # If a node comes in as the plain type but starts with one of the special
63
+ # characters that haml parses, then we need to escape it with a \ when
64
+ # printing. So here we make a regexp pattern to check if the node needs to be
65
+ # escaped.
66
+ special_chars =
67
+ Haml::Parser::SPECIAL_CHARACTERS.map { |char| Regexp.escape(char) }
68
+
69
+ SPECIAL_START = /\A(?:#{special_chars.join('|')})/
70
+
71
+ def as_json
72
+ case type
73
+ when :comment, :doctype, :silent_script
74
+ to_h.tap do |json|
75
+ json.delete(:parent)
76
+ json[:children] = children.map(&:as_json)
77
+ end
78
+ when :filter, :haml_comment
79
+ to_h.tap { |json| json.delete(:parent) }
80
+ when :plain
81
+ to_h.tap do |json|
82
+ json.delete(:parent)
83
+ json[:children] = children.map(&:as_json)
84
+
85
+ text = json[:value][:text]
86
+ json[:value][:text] = "\\#{text}" if text.match?(SPECIAL_START)
87
+ end
88
+ when :root
89
+ to_h.tap { |json| json[:children] = children.map(&:as_json) }
90
+ when :script
91
+ to_h.tap do |json|
92
+ json.delete(:parent)
93
+ json[:children] = children.map(&:as_json)
94
+
95
+ if json[:value][:text].match?(ESCAPE)
96
+ json[:value][:text].gsub!(ESCAPE) { $1 }
97
+ json[:value].merge!(escape_html: 'escape_html', interpolate: true)
98
+ end
99
+ end
100
+ when :tag
101
+ to_h.tap do |json|
102
+ json.delete(:parent)
103
+
104
+ # For some reason this is actually using a symbol to represent a null
105
+ # object ref instead of nil itself, so just replacing it here for
106
+ # simplicity in the printer
107
+ json[:value][:object_ref] = nil if json[:value][:object_ref] == :nil
108
+
109
+ # Get a reference to the dynamic attributes hash
110
+ dynamic_attributes = value[:dynamic_attributes].to_h
111
+
112
+ # If we have any in the old style, then we're going to pass it through
113
+ # the deep attribute parser filter.
114
+ if dynamic_attributes[:old]
115
+ dynamic_attributes[:old] =
116
+ DeepAttributeParser.new.parse(dynamic_attributes[:old])
117
+ end
118
+
119
+ json.merge!(
120
+ children: children.map(&:as_json),
121
+ value: value.merge(dynamic_attributes: dynamic_attributes)
122
+ )
123
+ end
124
+ else
125
+ raise ArgumentError, "Unsupported type: #{type}"
126
+ end
127
+ end
128
+ end
129
+
130
+ module Prettier
131
+ class HAMLParser
132
+ def self.parse(source)
133
+ Haml::Parser.new({}).call(source).as_json
134
+ rescue StandardError
135
+ false
136
+ end
137
+ end
138
+ end
@@ -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['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
+ 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