prettier 0.20.1 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,7 +7,9 @@ const {
7
7
  indent,
8
8
  softline
9
9
  } = require("../prettier");
10
+
10
11
  const { containsAssignment } = require("../utils");
12
+ const inlineEnsureParens = require("../utils/inlineEnsureParens");
11
13
 
12
14
  const printLoop = (keyword, modifier) => (path, { inlineLoops }, print) => {
13
15
  const [_predicate, statements] = path.getValue().body;
@@ -26,30 +28,13 @@ const printLoop = (keyword, modifier) => (path, { inlineLoops }, print) => {
26
28
  );
27
29
  }
28
30
 
29
- let inlineParts = [
30
- path.call(print, "body", 1),
31
- ` ${keyword} `,
32
- path.call(print, "body", 0)
33
- ];
34
-
35
- // If the return value of this loop expression is being assigned to anything
36
- // besides a local variable then we can't inline the entire expression
37
- // without wrapping it in parentheses. This is because the following
38
- // expressions have different semantic meaning:
39
- //
40
- // hash[:key] = break :value while false
41
- // hash[:key] = while false do break :value end
42
- //
43
- // The first one will not result in an empty hash, whereas the second one
44
- // will result in `{ key: nil }`. In this case what we need to do for the
45
- // first expression to align is wrap it in parens, as in:
46
- //
47
- // hash[:key] = (break :value while false)
48
- if (["assign", "massign"].includes(path.getParentNode().type)) {
49
- inlineParts = ["("].concat(inlineParts).concat(")");
50
- }
51
-
52
- const inlineLoop = concat(inlineParts);
31
+ const inlineLoop = concat(
32
+ inlineEnsureParens(path, [
33
+ path.call(print, "body", 1),
34
+ ` ${keyword} `,
35
+ path.call(print, "body", 0)
36
+ ])
37
+ );
53
38
 
54
39
  // If we're in the modifier form and we're modifying a `begin`, then this is a
55
40
  // special case where we need to explicitly use the modifier form because
@@ -133,12 +133,6 @@ module.exports = {
133
133
  const stringLiteral = path.getValue();
134
134
  const string = stringLiteral.body[0];
135
135
 
136
- // If this string is actually a heredoc, bail out and return to the print
137
- // function for heredocs
138
- if (string.type === "heredoc") {
139
- return path.call(print, "body", 0);
140
- }
141
-
142
136
  // If the string is empty, it will not have any parts, so just print out the
143
137
  // quotes corresponding to the config
144
138
  if (string.body.length === 0) {
@@ -19,7 +19,7 @@ const LANG = {
19
19
  module.exports = (text, _parsers, _opts) => {
20
20
  const child = spawnSync(
21
21
  "ruby",
22
- ["--disable-gems", path.join(__dirname, "./ripper.rb")],
22
+ ["--disable-gems", path.join(__dirname, "./parser.rb")],
23
23
  {
24
24
  env: Object.assign({}, process.env, { LANG }),
25
25
  input: text,
@@ -16,7 +16,9 @@ end
16
16
  require 'json' unless defined?(JSON)
17
17
  require 'ripper'
18
18
 
19
- class RipperJS < Ripper
19
+ module Prettier; end
20
+
21
+ class Prettier::Parser < Ripper
20
22
  attr_reader :source, :lines, :__end__
21
23
 
22
24
  def initialize(source, *args)
@@ -476,7 +478,7 @@ class RipperJS < Ripper
476
478
  def on_comment(body)
477
479
  sexp = { type: :@comment, body: body.chomp, start: lineno, end: lineno }
478
480
 
479
- case RipperJS.lex_state_name(state).gsub('EXPR_', '')
481
+ case Prettier::Parser.lex_state_name(state).gsub('EXPR_', '')
480
482
  when 'END', 'ARG|LABELED', 'ENDFN'
481
483
  last_sexp.merge!(comments: [sexp])
482
484
  when 'CMDARG', 'END|ENDARG', 'ENDARG', 'ARG', 'FNAME|FITEM', 'CLASS',
@@ -565,7 +567,7 @@ class RipperJS < Ripper
565
567
 
566
568
  def on_comment(body)
567
569
  super(body).tap do |sexp|
568
- lex_state = RipperJS.lex_state_name(state).gsub('EXPR_', '')
570
+ lex_state = Prettier::Parser.lex_state_name(state).gsub('EXPR_', '')
569
571
  block_comments << sexp if lex_state == 'BEG'
570
572
  end
571
573
  end
@@ -614,34 +616,39 @@ class RipperJS < Ripper
614
616
 
615
617
  private
616
618
 
617
- def on_embexpr_beg(body)
618
- super(body).tap { |sexp| heredoc_stack << sexp }
619
- end
620
-
621
- def on_embexpr_end(body)
622
- super(body).tap { heredoc_stack.pop }
623
- end
624
-
619
+ # This is a scanner event that represents the beginning of the heredoc.
625
620
  def on_heredoc_beg(beging)
626
- heredoc = { type: :heredoc, beging: beging, start: lineno, end: lineno }
627
- heredoc_stack << heredoc
621
+ {
622
+ type: :heredoc,
623
+ beging: beging,
624
+ start: lineno,
625
+ end: lineno,
626
+ char_start: char_pos - beging.length + 1,
627
+ char_end: char_pos
628
+ }.tap { |node| heredoc_stack << node }
628
629
  end
629
630
 
631
+ # This is a scanner event that represents the end of the heredoc.
630
632
  def on_heredoc_end(ending)
631
- heredoc_stack[-1].merge!(ending: ending.chomp, end: lineno)
633
+ heredoc_stack[-1].merge!(
634
+ ending: ending.chomp, end: lineno, char_end: char_pos
635
+ )
632
636
  end
633
637
 
638
+ # This is a parser event that occurs when you're using a heredoc with a
639
+ # tilde. These are considered `heredoc_dedent` nodes, whereas the hyphen
640
+ # heredocs show up as string literals.
634
641
  def on_heredoc_dedent(string, _width)
635
- heredoc = heredoc_stack.pop
636
- string.merge!(heredoc.slice(:type, :beging, :ending, :start, :end))
642
+ heredoc_stack[-1].merge!(string.slice(:body))
637
643
  end
638
644
 
645
+ # String literals are either going to be a normal string or they're going
646
+ # to be a heredoc with a hyphen.
639
647
  def on_string_literal(string)
640
648
  heredoc = heredoc_stack[-1]
641
649
 
642
- if heredoc && string[:type] != :heredoc && heredoc[:type] == :heredoc
643
- heredoc_stack.pop
644
- string.merge!(heredoc.slice(:type, :beging, :ending, :start, :end))
650
+ if heredoc && heredoc[:ending]
651
+ heredoc_stack.pop.merge!(string.slice(:body))
645
652
  else
646
653
  super
647
654
  end
@@ -731,36 +738,6 @@ class RipperJS < Ripper
731
738
  super(ident, params, def_bodystmt)
732
739
  end
733
740
 
734
- # By default, Ripper parses the expression `lambda { foo }` as a
735
- # `method_add_block` node, so we can't turn it back into `-> { foo }`.
736
- # This module overrides that behavior and reports it back as a `lambda`
737
- # node instead.
738
- def on_method_add_block(invocation, block)
739
- # It's possible to hit a `method_add_block` node without going through
740
- # `method_add_arg` node, ex: `super {}`. In that case we're definitely
741
- # not going to transform into a lambda.
742
- return super if invocation[:type] != :method_add_arg
743
-
744
- fcall, args = invocation[:body]
745
-
746
- # If there are arguments to the `lambda`, that means `lambda` has been
747
- # overridden as a function so we cannot transform it into a `lambda`
748
- # node.
749
- if fcall[:type] != :fcall || args[:type] != :args || args[:body].any?
750
- return super
751
- end
752
-
753
- ident = fcall.dig(:body, 0)
754
- return super if ident[:type] != :@ident || ident[:body] != 'lambda'
755
-
756
- super.tap do |sexp|
757
- params, stmts = block[:body]
758
- params ||= { type: :params, body: [] }
759
-
760
- sexp.merge!(type: :lambda, body: [params, stmts])
761
- end
762
- end
763
-
764
741
  # We need to track for `mlhs_paren` and `massign` nodes whether or not
765
742
  # there was an extra comma at the end of the expression. For some reason
766
743
  # it's not showing up in the AST in an obvious way. In this case we're
@@ -794,7 +771,7 @@ end
794
771
  # stdin and report back the AST over stdout.
795
772
 
796
773
  if $0 == __FILE__
797
- builder = RipperJS.new($stdin.read)
774
+ builder = Prettier::Parser.new($stdin.read)
798
775
  response = builder.parse
799
776
 
800
777
  if !response || builder.error?
@@ -130,7 +130,7 @@ module.exports = {
130
130
  toProcTransform: {
131
131
  type: "boolean",
132
132
  category: "Global",
133
- default: true,
133
+ default: false,
134
134
  description:
135
135
  "When possible, convert blocks to the more concise Symbol#to_proc syntax."
136
136
  }
@@ -5,6 +5,7 @@ const {
5
5
  lineSuffix,
6
6
  literalline
7
7
  } = require("./prettier");
8
+ const isEmptyStmts = require("./utils/isEmptyStmts");
8
9
 
9
10
  const concatBody = (path, opts, print) => concat(path.map(print, "body"));
10
11
 
@@ -65,27 +66,16 @@ const makeArgs = (path, opts, print, argsIndex) => {
65
66
  const heredocs = [];
66
67
 
67
68
  argNodes.body.forEach((argNode, index) => {
68
- let pattern;
69
- let heredoc;
70
-
71
69
  if (argNode.type === "heredoc") {
72
- pattern = [index, "body"];
73
- heredoc = argNode;
74
- } else if (
75
- argNode.type === "string_literal" &&
76
- argNode.body[0].type === "heredoc"
77
- ) {
78
- pattern = [index, "body", 0, "body"];
79
- [heredoc] = argNode.body;
80
- } else {
81
- return;
70
+ const content = path.map.apply(
71
+ path,
72
+ argPattern.slice().concat([index, "body"])
73
+ );
74
+ heredocs.push(
75
+ concat([literalline].concat(content).concat([argNode.ending]))
76
+ );
77
+ args[index] = argNode.beging;
82
78
  }
83
-
84
- const content = path.map.apply(path, argPattern.slice().concat(pattern));
85
- heredocs.push(
86
- concat([literalline].concat(content).concat([heredoc.ending]))
87
- );
88
- args[index] = heredoc.beging;
89
79
  });
90
80
 
91
81
  return { args, heredocs };
@@ -103,6 +93,16 @@ const makeCall = (path, opts, print) => {
103
93
 
104
94
  const makeList = (path, opts, print) => path.map(print, "body");
105
95
 
96
+ const nodeDive = (node, steps) => {
97
+ let current = node;
98
+
99
+ steps.forEach((step) => {
100
+ current = current[step];
101
+ });
102
+
103
+ return current;
104
+ };
105
+
106
106
  const prefix = (value) => (path, opts, print) =>
107
107
  concat([value, path.call(print, "body", 0)]);
108
108
 
@@ -146,10 +146,12 @@ module.exports = {
146
146
  empty,
147
147
  first,
148
148
  hasAncestor,
149
+ isEmptyStmts,
149
150
  literal,
150
151
  makeArgs,
151
152
  makeCall,
152
153
  makeList,
154
+ nodeDive,
153
155
  prefix,
154
156
  printComments,
155
157
  skipAssignIndent,
@@ -0,0 +1,42 @@
1
+ const needsParens = ["args", "assign", "assoc_new", "massign", "opassign"];
2
+
3
+ // If you have a modifier statement (for instance an inline if statement or an
4
+ // inline while loop) there are times when you need to wrap the entire statement
5
+ // in parentheses. This occurs when you have something like:
6
+ //
7
+ // foo[:foo] =
8
+ // if bar?
9
+ // baz
10
+ // end
11
+ //
12
+ // Normally we would shorten this to an inline version, which would result in:
13
+ //
14
+ // foo[:foo] = baz if bar?
15
+ //
16
+ // but this actually has different semantic meaning. The first example will
17
+ // result in a nil being inserted into the hash for the :foo key, whereas the
18
+ // second example will result in an empty hash because the if statement applies
19
+ // to the entire assignment.
20
+ //
21
+ // We can fix this in a couple of ways. We can use the then keyword, as in:
22
+ //
23
+ // foo[:foo] = if bar? then baz end
24
+ //
25
+ // but I haven't actually seen this anywhere. We can also just leave it as is
26
+ // with the multi-line version, but for a short predicate and short value it
27
+ // looks pretty silly. The last option and the one I've selected here is to add
28
+ // parentheses on both sides of the expression, as in:
29
+ //
30
+ // foo[:foo] = (baz if bar?)
31
+ //
32
+ // This approach maintains the nice conciseness of the inline version, while
33
+ // keeping the correct semantic meaning.
34
+ const inlineEnsureParens = (path, parts) => {
35
+ if (needsParens.includes(path.getParentNode().type)) {
36
+ return ["("].concat(parts, ")");
37
+ }
38
+
39
+ return parts;
40
+ };
41
+
42
+ module.exports = inlineEnsureParens;
@@ -0,0 +1,7 @@
1
+ const isEmptyStmts = (node) =>
2
+ node &&
3
+ node.type === "stmts" &&
4
+ node.body.length === 1 &&
5
+ node.body[0].type === "void_stmt";
6
+
7
+ module.exports = isEmptyStmts;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prettier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.1
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Deisz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-04 00:00:00.000000000 Z
11
+ date: 2020-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -75,6 +75,7 @@ files:
75
75
  - src/embed.js
76
76
  - src/nodes.js
77
77
  - src/nodes/alias.js
78
+ - src/nodes/aref.js
78
79
  - src/nodes/args.js
79
80
  - src/nodes/arrays.js
80
81
  - src/nodes/assign.js
@@ -102,12 +103,14 @@ files:
102
103
  - src/nodes/statements.js
103
104
  - src/nodes/strings.js
104
105
  - src/parse.js
106
+ - src/parser.rb
105
107
  - src/prettier.js
106
108
  - src/print.js
107
- - src/ripper.rb
108
109
  - src/ruby.js
109
110
  - src/toProc.js
110
111
  - src/utils.js
112
+ - src/utils/inlineEnsureParens.js
113
+ - src/utils/isEmptyStmts.js
111
114
  homepage: https://github.com/prettier/plugin-ruby#readme
112
115
  licenses:
113
116
  - MIT