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
@@ -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