prettier 1.2.5 → 1.5.2

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 (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
@@ -1,5 +1,5 @@
1
1
  const { concat, group, lineSuffix, join } = require("../../prettier");
2
- const { literalLineNoBreak } = require("../../utils");
2
+ const { literallineWithoutBreakParent } = require("../../utils");
3
3
 
4
4
  function printHeredoc(path, opts, print) {
5
5
  const { body, ending } = path.getValue();
@@ -11,7 +11,7 @@ function printHeredoc(path, opts, print) {
11
11
  }
12
12
 
13
13
  // In this case, the part of the string is just regular string content
14
- return join(literalLineNoBreak, part.body.split("\n"));
14
+ return join(literallineWithoutBreakParent, part.body.split("\n"));
15
15
  });
16
16
 
17
17
  // We use a literalline break because matching indentation is required
@@ -23,7 +23,9 @@ function printHeredoc(path, opts, print) {
23
23
  concat([
24
24
  path.call(print, "beging"),
25
25
  lineSuffix(
26
- group(concat([literalLineNoBreak].concat(parts).concat(ending)))
26
+ group(
27
+ concat([literallineWithoutBreakParent].concat(parts).concat(ending))
28
+ )
27
29
  )
28
30
  ])
29
31
  );
@@ -10,7 +10,7 @@ const {
10
10
  softline
11
11
  } = require("../../prettier");
12
12
 
13
- const { containsAssignment } = require("../../utils");
13
+ const { containsAssignment, isEmptyStmts } = require("../../utils");
14
14
  const inlineEnsureParens = require("../../utils/inlineEnsureParens");
15
15
 
16
16
  function printLoop(keyword, modifier) {
@@ -19,17 +19,11 @@ function printLoop(keyword, modifier) {
19
19
 
20
20
  // If the only statement inside this while loop is a void statement, then we
21
21
  // can shorten to just displaying the predicate and then a semicolon.
22
- if (
23
- stmts.body.length === 1 &&
24
- stmts.body[0].type === "void_stmt" &&
25
- !stmts.body[0].comments
26
- ) {
22
+ if (isEmptyStmts(stmts)) {
27
23
  return group(
28
24
  concat([
29
- keyword,
30
- " ",
31
- path.call(print, "body", 0),
32
- ifBreak(softline, "; "),
25
+ group(concat([keyword, " ", path.call(print, "body", 0)])),
26
+ hardline,
33
27
  "end"
34
28
  ])
35
29
  );
@@ -1,8 +1,9 @@
1
1
  const { concat, group, hardline, indent, line } = require("../../prettier");
2
+ const { isEmptyBodyStmt } = require("../../utils");
2
3
 
3
4
  function printMethod(offset) {
4
5
  return function printMethodWithOffset(path, opts, print) {
5
- const [_name, params, body] = path.getValue().body.slice(offset);
6
+ const [_name, params, bodystmt] = path.getValue().body.slice(offset);
6
7
  const declaration = ["def "];
7
8
 
8
9
  // In this case, we're printing a method that's defined as a singleton, so
@@ -24,16 +25,8 @@ function printMethod(offset) {
24
25
  parens ? ")" : ""
25
26
  );
26
27
 
27
- // If the body is empty, we can replace with a ;
28
- const stmts = body.body[0].body;
29
-
30
- if (
31
- !body.body.slice(1).some((node) => node) &&
32
- stmts.length === 1 &&
33
- stmts[0].type === "void_stmt" &&
34
- !stmts[0].comments
35
- ) {
36
- return group(concat(declaration.concat(["; end"])));
28
+ if (isEmptyBodyStmt(bodystmt)) {
29
+ return group(concat(declaration.concat("; end")));
37
30
  }
38
31
 
39
32
  return group(
@@ -26,28 +26,10 @@ function printEnsure(path, opts, print) {
26
26
  }
27
27
 
28
28
  function printRescue(path, opts, print) {
29
- const [exception, variable, _stmts, addition] = path.getValue().body;
30
29
  const parts = ["rescue"];
31
30
 
32
- if (exception || variable) {
33
- if (exception) {
34
- if (Array.isArray(exception)) {
35
- // In this case, it's actually only the one exception (it's an array
36
- // of length 1).
37
- parts.push(" ", path.call(print, "body", 0, 0));
38
- } else {
39
- // Here we have multiple exceptions from which we're rescuing, so we
40
- // need to align them and join them together.
41
- const joiner = concat([",", line]);
42
- const exceptions = group(join(joiner, path.call(print, "body", 0)));
43
-
44
- parts.push(" ", align("rescue ".length, exceptions));
45
- }
46
- }
47
-
48
- if (variable) {
49
- parts.push(" => ", path.call(print, "body", 1));
50
- }
31
+ if (path.getValue().body[0]) {
32
+ parts.push(align("rescue ".length, path.call(print, "body", 0)));
51
33
  } else {
52
34
  // If you don't specify an error to rescue in a `begin/rescue` block, then
53
35
  // implicitly you're rescuing from `StandardError`. In this case, we're
@@ -55,16 +37,40 @@ function printRescue(path, opts, print) {
55
37
  parts.push(" StandardError");
56
38
  }
57
39
 
58
- const rescueBody = path.call(print, "body", 2);
40
+ const bodystmt = path.call(print, "body", 1);
59
41
 
60
- if (rescueBody.parts.length > 0) {
61
- parts.push(indent(concat([hardline, rescueBody])));
42
+ if (bodystmt.parts.length > 0) {
43
+ parts.push(indent(concat([hardline, bodystmt])));
62
44
  }
63
45
 
64
46
  // This is the next clause on the `begin` statement, either another
65
47
  // `rescue`, and `ensure`, or an `else` clause.
66
- if (addition) {
67
- parts.push(concat([hardline, path.call(print, "body", 3)]));
48
+ if (path.getValue().body[2]) {
49
+ parts.push(concat([hardline, path.call(print, "body", 2)]));
50
+ }
51
+
52
+ return group(concat(parts));
53
+ }
54
+
55
+ // This is a container node that we're adding into the AST that isn't present in
56
+ // Ripper solely so that we have a nice place to attach inline comments.
57
+ function printRescueEx(path, opts, print) {
58
+ const [exception, variable] = path.getValue().body;
59
+ const parts = [];
60
+
61
+ if (exception) {
62
+ let exceptionDoc = path.call(print, "body", 0);
63
+
64
+ if (Array.isArray(exceptionDoc)) {
65
+ const joiner = concat([",", line]);
66
+ exceptionDoc = group(join(joiner, exceptionDoc));
67
+ }
68
+
69
+ parts.push(" ", exceptionDoc);
70
+ }
71
+
72
+ if (variable) {
73
+ parts.push(" => ", path.call(print, "body", 1));
68
74
  }
69
75
 
70
76
  return group(concat(parts));
@@ -89,6 +95,7 @@ module.exports = {
89
95
  ensure: printEnsure,
90
96
  redo: literal("redo"),
91
97
  rescue: printRescue,
98
+ rescue_ex: printRescueEx,
92
99
  rescue_mod: printRescueMod,
93
100
  retry: literal("retry")
94
101
  };
@@ -12,15 +12,13 @@ const {
12
12
  trim
13
13
  } = require("../../prettier");
14
14
 
15
+ const { isEmptyStmts } = require("../../utils");
16
+
15
17
  function printBodyStmt(path, opts, print) {
16
18
  const [stmts, rescue, elseClause, ensure] = path.getValue().body;
17
19
  const parts = [];
18
20
 
19
- if (
20
- stmts.body.length > 1 ||
21
- stmts.body[0].type != "void_stmt" ||
22
- stmts.body[0].comments
23
- ) {
21
+ if (!isEmptyStmts(stmts)) {
24
22
  parts.push(path.call(print, "body", 0));
25
23
  }
26
24
 
@@ -79,6 +77,9 @@ module.exports = {
79
77
  const { body } = path.getValue();
80
78
  return concat([trim, "__END__", literalline, body]);
81
79
  },
80
+ "@comment"(path, opts, _print) {
81
+ return opts.printer.printComment(path);
82
+ },
82
83
  bodystmt: printBodyStmt,
83
84
  paren: printParen,
84
85
  program: (path, opts, print) =>
@@ -4,6 +4,7 @@ const {
4
4
  hardline,
5
5
  indent,
6
6
  literalline,
7
+ removeLines,
7
8
  softline,
8
9
  join
9
10
  } = require("../../prettier");
@@ -103,14 +104,14 @@ function printStringDVar(path, opts, print) {
103
104
  }
104
105
 
105
106
  function printStringEmbExpr(path, opts, print) {
107
+ const node = path.getValue();
106
108
  const parts = path.call(print, "body", 0);
107
109
 
108
- // If the interpolated expression is inside of a heredoc or an xstring
109
- // literal (a string that gets sent to the command line) then we don't want
110
- // to automatically indent, as this can lead to some very odd looking
111
- // expressions
112
- if (["heredoc", "xstring_literal"].includes(path.getParentNode().type)) {
113
- return concat(["#{", parts, "}"]);
110
+ // If the contents of this embedded expression were originally on the same
111
+ // line in the source, then we're going to leave them in place and assume
112
+ // that's the way the developer wanted this expression represented.
113
+ if (node.sl === node.el) {
114
+ return concat(["#{", removeLines(parts), "}"]);
114
115
  }
115
116
 
116
117
  return group(
data/src/ruby/parser.js CHANGED
@@ -1,59 +1,11 @@
1
- const { spawnSync } = require("child_process");
2
- const path = require("path");
3
-
4
- // In order to properly parse ruby code, we need to tell the ruby process to
5
- // parse using UTF-8. Unfortunately, the way that you accomplish this looks
6
- // differently depending on your platform.
7
- /* istanbul ignore next */
8
- const LANG = (() => {
9
- const { env, platform } = process;
10
- const envValue = env.LC_ALL || env.LC_CTYPE || env.LANG;
11
-
12
- // If an env var is set for the locale that already includes UTF-8 in the
13
- // name, then assume we can go with that.
14
- if (envValue && envValue.includes("UTF-8")) {
15
- return envValue;
16
- }
17
-
18
- // Otherwise, we're going to guess which encoding to use based on the system.
19
- // This is probably not the best approach in the world, as you could be on
20
- // linux and not have C.UTF-8, but in that case you're probably passing an env
21
- // var for it. This object below represents all of the possible values of
22
- // process.platform per:
23
- // https://nodejs.org/api/process.html#process_process_platform
24
- return {
25
- aix: "C.UTF-8",
26
- darwin: "en_US.UTF-8",
27
- freebsd: "C.UTF-8",
28
- linux: "C.UTF-8",
29
- openbsd: "C.UTF-8",
30
- sunos: "C.UTF-8",
31
- win32: ".UTF-8"
32
- }[platform];
33
- })();
1
+ const parseSync = require("../parser/parseSync");
34
2
 
35
3
  // This function is responsible for taking an input string of text and returning
36
4
  // to prettier a JavaScript object that is the equivalent AST that represents
37
5
  // the code stored in that string. We accomplish this by spawning a new Ruby
38
6
  // process of parser.rb and reading JSON off STDOUT.
39
7
  function parse(text, _parsers, _opts) {
40
- const child = spawnSync(
41
- "ruby",
42
- ["--disable-gems", path.join(__dirname, "./parser.rb")],
43
- {
44
- env: Object.assign({}, process.env, { LANG }),
45
- input: text,
46
- maxBuffer: 15 * 1024 * 1024 // 15MB
47
- }
48
- );
49
-
50
- const error = child.stderr.toString();
51
- if (error) {
52
- throw new Error(error);
53
- }
54
-
55
- const response = child.stdout.toString();
56
- return JSON.parse(response);
8
+ return parseSync("ruby", text);
57
9
  }
58
10
 
59
11
  const pragmaPattern = /#\s*@(prettier|format)/;
data/src/ruby/parser.rb CHANGED
@@ -14,13 +14,51 @@ if (RUBY_MAJOR < 2) || ((RUBY_MAJOR == 2) && (RUBY_MINOR < 5))
14
14
  end
15
15
 
16
16
  require 'delegate'
17
- require 'json' unless defined?(JSON)
17
+ require 'json'
18
18
  require 'ripper'
19
19
 
20
- module Prettier; end
20
+ module Prettier
21
+ end
21
22
 
22
23
  class Prettier::Parser < Ripper
23
- attr_reader :source, :lines, :scanner_events, :line_counts
24
+ # Represents a line in the source. If this class is being used, it means that
25
+ # every character in the string is 1 byte in length, so we can just return the
26
+ # start of the line + the index.
27
+ class SingleByteString
28
+ def initialize(start)
29
+ @start = start
30
+ end
31
+
32
+ def [](byteindex)
33
+ @start + byteindex
34
+ end
35
+ end
36
+
37
+ # Represents a line in the source. If this class is being used, it means that
38
+ # there are characters in the string that are multi-byte, so we will build up
39
+ # an array of indices, such that array[byteindex] will be equal to the index
40
+ # of the character within the string.
41
+ class MultiByteString
42
+ def initialize(start, line)
43
+ @indices = []
44
+
45
+ line
46
+ .each_char
47
+ .with_index(start) do |char, index|
48
+ char.bytesize.times { @indices << index }
49
+ end
50
+ end
51
+
52
+ def [](byteindex)
53
+ @indices[byteindex]
54
+ end
55
+ end
56
+
57
+ attr_reader :source, :lines, :scanner_events
58
+
59
+ # This is an attr_accessor so Stmts objects can grab comments out of this
60
+ # array and attach them to themselves.
61
+ attr_accessor :comments
24
62
 
25
63
  def initialize(source, *args)
26
64
  super(source, *args)
@@ -35,9 +73,30 @@ class Prettier::Parser < Ripper
35
73
  @heredocs = []
36
74
 
37
75
  @scanner_events = []
38
- @line_counts = [0]
76
+ @line_counts = []
77
+
78
+ # Here we're going to build up a list of SingleByteString or MultiByteString
79
+ # objects. They're each going to represent a string in the source. They are
80
+ # used by the `char_pos` method to determine where we are in the source
81
+ # string.
82
+ last_index = 0
83
+
84
+ @source.lines.each do |line|
85
+ if line.size == line.bytesize
86
+ @line_counts << SingleByteString.new(last_index)
87
+ else
88
+ @line_counts << MultiByteString.new(last_index, line)
89
+ end
39
90
 
40
- @source.lines.each { |line| @line_counts << @line_counts.last + line.size }
91
+ last_index += line.size
92
+ end
93
+ end
94
+
95
+ def self.parse(source)
96
+ builder = new(source)
97
+
98
+ response = builder.parse
99
+ response unless builder.error?
41
100
  end
42
101
 
43
102
  private
@@ -48,7 +107,7 @@ class Prettier::Parser < Ripper
48
107
  # this line, then we add the number of columns into this line that we've gone
49
108
  # through.
50
109
  def char_pos
51
- line_counts[lineno - 1] + column
110
+ @line_counts[lineno - 1][column]
52
111
  end
53
112
 
54
113
  # As we build up a list of scanner events, we'll periodically need to go
@@ -118,6 +177,7 @@ class Prettier::Parser < Ripper
118
177
  @comments << {
119
178
  type: :@comment,
120
179
  value: value[1..-1].chomp.force_encoding('UTF-8'),
180
+ inline: value.strip != lines[lineno - 1],
121
181
  sl: lineno,
122
182
  el: lineno,
123
183
  sc: char_pos,
@@ -505,6 +565,12 @@ class Prettier::Parser < Ripper
505
565
  # binary is a parser event that represents a binary operation between two
506
566
  # values.
507
567
  def on_binary(left, oper, right)
568
+ # On most Ruby implementations, oper is a Symbol that represents that
569
+ # operation being performed. For instance in the example `1 < 2`, the `oper`
570
+ # object would be `:<`. However, on JRuby, it's an `@op` node, so here we're
571
+ # going to explicitly convert it into the same normalized form.
572
+ oper = scanner_events.delete(oper)[:body] unless oper.is_a?(Symbol)
573
+
508
574
  {
509
575
  type: :binary,
510
576
  body: [left, oper, right],
@@ -1761,8 +1827,8 @@ class Prettier::Parser < Ripper
1761
1827
  def bind_end(ec)
1762
1828
  merge!(ec: ec)
1763
1829
 
1764
- stmts = self[:body][2]
1765
- consequent = self[:body][3]
1830
+ stmts = self[:body][1]
1831
+ consequent = self[:body][2]
1766
1832
 
1767
1833
  if consequent
1768
1834
  consequent.bind_end(ec)
@@ -1777,16 +1843,30 @@ class Prettier::Parser < Ripper
1777
1843
  # inside of a bodystmt.
1778
1844
  def on_rescue(exceptions, variable, stmts, consequent)
1779
1845
  beging = find_scanner_event(:@kw, 'rescue')
1846
+ exceptions = exceptions[0] if exceptions.is_a?(Array)
1780
1847
 
1781
- last_exception = exceptions.is_a?(Array) ? exceptions[-1] : exceptions
1782
- last_node = variable || last_exception || beging
1783
-
1848
+ last_node = variable || exceptions || beging
1784
1849
  stmts.bind(find_next_statement_start(last_node[:ec]), char_pos)
1785
1850
 
1851
+ # We add an additional inner node here that ripper doesn't provide so that
1852
+ # we have a nice place to attach inline comment. But we only need it if we
1853
+ # have an exception or a variable that we're rescuing.
1854
+ rescue_ex =
1855
+ if exceptions || variable
1856
+ {
1857
+ type: :rescue_ex,
1858
+ body: [exceptions, variable],
1859
+ sl: beging[:sl],
1860
+ sc: beging[:ec] + 1,
1861
+ el: last_node[:el],
1862
+ ec: last_node[:ec]
1863
+ }
1864
+ end
1865
+
1786
1866
  Rescue.new(
1787
1867
  beging.merge!(
1788
1868
  type: :rescue,
1789
- body: [exceptions, variable, stmts, consequent],
1869
+ body: [rescue_ex, stmts, consequent],
1790
1870
  el: lineno,
1791
1871
  ec: char_pos
1792
1872
  )
@@ -1885,12 +1965,21 @@ class Prettier::Parser < Ripper
1885
1965
  # propagate that onto void_stmt nodes inside the stmts in order to make sure
1886
1966
  # all comments get printed appropriately.
1887
1967
  class Stmts < SimpleDelegator
1968
+ attr_reader :parser
1969
+
1970
+ def initialize(parser, values)
1971
+ @parser = parser
1972
+ __setobj__(values)
1973
+ end
1974
+
1888
1975
  def bind(sc, ec)
1889
1976
  merge!(sc: sc, ec: ec)
1890
1977
 
1891
1978
  if self[:body][0][:type] == :void_stmt
1892
1979
  self[:body][0].merge!(sc: sc, ec: sc)
1893
1980
  end
1981
+
1982
+ attach_comments(sc, ec)
1894
1983
  end
1895
1984
 
1896
1985
  def bind_end(ec)
@@ -1907,6 +1996,22 @@ class Prettier::Parser < Ripper
1907
1996
  self[:body] << statement
1908
1997
  self
1909
1998
  end
1999
+
2000
+ private
2001
+
2002
+ def attach_comments(sc, ec)
2003
+ attachable =
2004
+ parser.comments.select do |comment|
2005
+ comment[:type] == :@comment && !comment[:inline] &&
2006
+ sc <= comment[:sc] && ec >= comment[:ec] &&
2007
+ !comment[:value].include?('prettier-ignore')
2008
+ end
2009
+
2010
+ return if attachable.empty?
2011
+
2012
+ parser.comments -= attachable
2013
+ self[:body] = (self[:body] + attachable).sort_by! { |node| node[:sc] }
2014
+ end
1910
2015
  end
1911
2016
 
1912
2017
  # stmts_new is a parser event that represents the beginning of a list of
@@ -1914,6 +2019,7 @@ class Prettier::Parser < Ripper
1914
2019
  # stmts_add events, which we'll append onto an array body.
1915
2020
  def on_stmts_new
1916
2021
  Stmts.new(
2022
+ self,
1917
2023
  type: :stmts,
1918
2024
  body: [],
1919
2025
  sl: lineno,
@@ -2542,24 +2648,3 @@ class Prettier::Parser < Ripper
2542
2648
  find_scanner_event(:@kw, 'super').merge!(type: :zsuper)
2543
2649
  end
2544
2650
  end
2545
-
2546
- # If this is the main file we're executing, then most likely this is being
2547
- # executed from the parser.js spawn. In that case, read the ruby source from
2548
- # stdin and report back the AST over stdout.
2549
-
2550
- if $0 == __FILE__
2551
- builder = Prettier::Parser.new($stdin.read)
2552
- response = builder.parse
2553
-
2554
- if !response || builder.error?
2555
- warn(
2556
- '@prettier/plugin-ruby encountered an error when attempting to parse ' \
2557
- 'the ruby source. This usually means there was a syntax error in the ' \
2558
- 'file in question. You can verify by running `ruby -i [path/to/file]`.'
2559
- )
2560
-
2561
- exit 1
2562
- end
2563
-
2564
- puts JSON.fast_generate(response)
2565
- end