prettier 1.2.2 → 1.4.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -1
  3. data/CONTRIBUTING.md +2 -2
  4. data/README.md +22 -94
  5. data/node_modules/prettier/index.js +54 -54
  6. data/package.json +2 -3
  7. data/rubocop.yml +26 -0
  8. data/src/haml/embed.js +87 -0
  9. data/src/haml/nodes/comment.js +27 -0
  10. data/src/haml/nodes/doctype.js +34 -0
  11. data/src/haml/nodes/filter.js +16 -0
  12. data/src/haml/nodes/hamlComment.js +21 -0
  13. data/src/haml/nodes/plain.js +6 -0
  14. data/src/haml/nodes/root.js +8 -0
  15. data/src/haml/nodes/script.js +33 -0
  16. data/src/haml/nodes/silentScript.js +59 -0
  17. data/src/haml/nodes/tag.js +193 -0
  18. data/src/haml/parser.js +22 -0
  19. data/src/haml/parser.rb +138 -0
  20. data/src/haml/printer.js +28 -0
  21. data/src/parser/getLang.js +32 -0
  22. data/src/parser/getNetcat.js +50 -0
  23. data/src/parser/netcat.js +15 -0
  24. data/src/parser/parseSync.js +33 -0
  25. data/src/parser/requestParse.js +74 -0
  26. data/src/parser/server.rb +61 -0
  27. data/src/{ruby.js → plugin.js} +25 -4
  28. data/src/prettier.js +1 -0
  29. data/src/rbs/parser.js +39 -0
  30. data/src/rbs/parser.rb +88 -0
  31. data/src/rbs/printer.js +605 -0
  32. data/src/{embed.js → ruby/embed.js} +6 -2
  33. data/src/{nodes.js → ruby/nodes.js} +0 -0
  34. data/src/{nodes → ruby/nodes}/alias.js +1 -1
  35. data/src/{nodes → ruby/nodes}/aref.js +8 -1
  36. data/src/{nodes → ruby/nodes}/args.js +2 -2
  37. data/src/{nodes → ruby/nodes}/arrays.js +2 -3
  38. data/src/{nodes → ruby/nodes}/assign.js +7 -3
  39. data/src/ruby/nodes/blocks.js +90 -0
  40. data/src/{nodes → ruby/nodes}/calls.js +20 -47
  41. data/src/{nodes → ruby/nodes}/case.js +1 -1
  42. data/src/{nodes → ruby/nodes}/class.js +1 -1
  43. data/src/ruby/nodes/commands.js +131 -0
  44. data/src/{nodes → ruby/nodes}/conditionals.js +3 -3
  45. data/src/{nodes → ruby/nodes}/constants.js +2 -2
  46. data/src/{nodes → ruby/nodes}/flow.js +2 -2
  47. data/src/{nodes → ruby/nodes}/hashes.js +32 -10
  48. data/src/{nodes → ruby/nodes}/heredocs.js +2 -2
  49. data/src/ruby/nodes/hooks.js +34 -0
  50. data/src/{nodes → ruby/nodes}/ints.js +0 -0
  51. data/src/{nodes → ruby/nodes}/lambdas.js +2 -2
  52. data/src/{nodes → ruby/nodes}/loops.js +10 -7
  53. data/src/{nodes → ruby/nodes}/massign.js +8 -1
  54. data/src/{nodes → ruby/nodes}/methods.js +10 -9
  55. data/src/{nodes → ruby/nodes}/operators.js +2 -2
  56. data/src/{nodes → ruby/nodes}/params.js +31 -16
  57. data/src/{nodes → ruby/nodes}/patterns.js +17 -6
  58. data/src/{nodes → ruby/nodes}/regexp.js +2 -2
  59. data/src/{nodes → ruby/nodes}/rescue.js +34 -27
  60. data/src/{nodes → ruby/nodes}/return.js +21 -10
  61. data/src/{nodes → ruby/nodes}/statements.js +9 -9
  62. data/src/{nodes → ruby/nodes}/strings.js +28 -36
  63. data/src/{nodes → ruby/nodes}/super.js +2 -2
  64. data/src/{nodes → ruby/nodes}/undef.js +1 -1
  65. data/src/ruby/parser.js +39 -0
  66. data/src/{parser.rb → ruby/parser.rb} +498 -529
  67. data/src/{printer.js → ruby/printer.js} +1 -3
  68. data/src/{toProc.js → ruby/toProc.js} +4 -8
  69. data/src/utils.js +10 -93
  70. data/src/utils/containsAssignment.js +11 -0
  71. data/src/utils/getTrailingComma.js +5 -0
  72. data/src/utils/hasAncestor.js +17 -0
  73. data/src/utils/literal.js +7 -0
  74. data/src/utils/makeCall.js +14 -0
  75. data/src/utils/noIndent.js +11 -0
  76. data/src/utils/skipAssignIndent.js +10 -0
  77. metadata +71 -41
  78. data/src/nodes/blocks.js +0 -85
  79. data/src/nodes/commands.js +0 -91
  80. data/src/nodes/hooks.js +0 -44
  81. data/src/parser.js +0 -86
@@ -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['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