prettier 0.15.1 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ const { concat, hardline, join, markAsRoot } = require("../prettier");
2
+
3
+ const comment = require("./nodes/comment");
4
+ const doctype = require("./nodes/doctype");
5
+ const filter = require("./nodes/filter");
6
+ const hamlComment = require("./nodes/hamlComment");
7
+ const script = require("./nodes/script");
8
+ const silentScript = require("./nodes/silentScript");
9
+ const tag = require("./nodes/tag");
10
+
11
+ const nodes = {
12
+ comment,
13
+ doctype,
14
+ filter,
15
+ haml_comment: hamlComment,
16
+ plain: (path, _opts, _print) => {
17
+ const { value } = path.getValue();
18
+
19
+ return value.text.startsWith("=") ? `\\${value.text}` : value.text;
20
+ },
21
+ root: (path, opts, print) =>
22
+ markAsRoot(concat([join(hardline, path.map(print, "children")), hardline])),
23
+ script,
24
+ silent_script: silentScript,
25
+ tag
26
+ };
27
+
28
+ const genericPrint = (path, opts, print) => {
29
+ const { type } = path.getValue();
30
+
31
+ if (!(type in nodes)) {
32
+ throw new Error(`Unsupported node encountered: ${type}`);
33
+ }
34
+
35
+ return nodes[type](path, opts, print);
36
+ };
37
+
38
+ module.exports = genericPrint;
@@ -23,6 +23,7 @@ module.exports = Object.assign(
23
23
  require("./nodes/patterns"),
24
24
  require("./nodes/regexp"),
25
25
  require("./nodes/rescue"),
26
+ require("./nodes/return"),
26
27
  require("./nodes/scopes"),
27
28
  require("./nodes/statements"),
28
29
  require("./nodes/strings")
@@ -10,34 +10,6 @@ const {
10
10
  } = require("../prettier");
11
11
  const { containsAssignment } = require("../utils");
12
12
 
13
- const noTernary = [
14
- "@comment",
15
- "alias",
16
- "assign",
17
- "break",
18
- "command",
19
- "command_call",
20
- "if_mod",
21
- "ifop",
22
- "lambda",
23
- "massign",
24
- "next",
25
- "opassign",
26
- "rescue_mod",
27
- "return",
28
- "return0",
29
- "super",
30
- "undef",
31
- "unless_mod",
32
- "until_mod",
33
- "var_alias",
34
- "void_stmt",
35
- "while_mod",
36
- "yield",
37
- "yield0",
38
- "zsuper"
39
- ];
40
-
41
13
  const printWithAddition = (keyword, path, print, { breaking = false } = {}) =>
42
14
  concat([
43
15
  `${keyword} `,
@@ -152,11 +124,54 @@ const printSingle = keyword => (path, { inlineConditionals }, print) => {
152
124
  return group(ifBreak(multiline, inline));
153
125
  };
154
126
 
127
+ const noTernary = [
128
+ "@comment",
129
+ "alias",
130
+ "assign",
131
+ "break",
132
+ "command",
133
+ "command_call",
134
+ "if_mod",
135
+ "ifop",
136
+ "lambda",
137
+ "massign",
138
+ "next",
139
+ "opassign",
140
+ "rescue_mod",
141
+ "return",
142
+ "return0",
143
+ "super",
144
+ "undef",
145
+ "unless_mod",
146
+ "until_mod",
147
+ "var_alias",
148
+ "void_stmt",
149
+ "while_mod",
150
+ "yield",
151
+ "yield0",
152
+ "zsuper"
153
+ ];
154
+
155
155
  // Certain expressions cannot be reduced to a ternary without adding parens
156
156
  // around them. In this case we say they cannot be ternaried and default instead
157
157
  // to breaking them into multiple lines.
158
- const canTernaryStmts = stmts =>
159
- stmts.body.length === 1 && !noTernary.includes(stmts.body[0].type);
158
+ const canTernaryStmts = stmts => {
159
+ if (stmts.body.length !== 1) {
160
+ return false;
161
+ }
162
+
163
+ const stmt = stmts.body[0];
164
+
165
+ // If the user is using one of the lower precedence "and" or "or" operators,
166
+ // then we can't use a ternary expression as it would break the flow control.
167
+ if (stmt.type === "binary" && ["and", "or"].includes(stmt.body[1])) {
168
+ return false;
169
+ }
170
+
171
+ // Check against the blocklist of statement types that are not allowed to be
172
+ // a part of a ternary expression.
173
+ return !noTernary.includes(stmt.type);
174
+ };
160
175
 
161
176
  // In order for an `if` or `unless` expression to be shortened to a ternary,
162
177
  // there has to be one and only one "addition" (another clause attached) which
@@ -1,11 +1,4 @@
1
- const {
2
- concat,
3
- group,
4
- ifBreak,
5
- indent,
6
- join,
7
- softline
8
- } = require("../prettier");
1
+ const { concat, join } = require("../prettier");
9
2
  const { literal } = require("../utils");
10
3
 
11
4
  module.exports = {
@@ -42,32 +35,6 @@ module.exports = {
42
35
 
43
36
  return concat(["next ", join(", ", path.call(print, "body", 0))]);
44
37
  },
45
- return: (path, opts, print) => {
46
- const args = path.getValue().body[0].body[0];
47
-
48
- if (!args) {
49
- return "return";
50
- }
51
-
52
- let value = join(", ", path.call(print, "body", 0));
53
-
54
- // If the body of the return contains parens, then just skip directly to the
55
- // content of the parens so that we can skip printing parens if we don't
56
- // want them.
57
- if (args.body[0] && args.body[0].type === "paren") {
58
- value = path.call(print, "body", 0, "body", 0, "body", 0, "body", 0);
59
- }
60
-
61
- return group(
62
- concat([
63
- "return",
64
- ifBreak("(", " "),
65
- indent(concat([softline, value])),
66
- concat([softline, ifBreak(")", "")])
67
- ])
68
- );
69
- },
70
- return0: literal("return"),
71
38
  yield: (path, opts, print) => {
72
39
  if (path.getValue().body[0].type === "paren") {
73
40
  return concat(["yield", path.call(print, "body", 0)]);
@@ -13,12 +13,13 @@ module.exports = {
13
13
  right = group(join(concat([",", line]), right));
14
14
  }
15
15
 
16
+ const parts = [join(concat([",", line]), path.call(print, "body", 0))];
17
+ if (path.getValue().body[0].comma) {
18
+ parts.push(",");
19
+ }
20
+
16
21
  return group(
17
- concat([
18
- group(join(concat([",", line]), path.call(print, "body", 0))),
19
- " =",
20
- indent(concat([line, right]))
21
- ])
22
+ concat([group(concat(parts)), " =", indent(concat([line, right]))])
22
23
  );
23
24
  },
24
25
  mlhs: makeList,
@@ -40,18 +41,16 @@ module.exports = {
40
41
  return path.call(print, "body", 0);
41
42
  }
42
43
 
43
- return group(
44
- concat([
45
- "(",
46
- indent(
47
- concat([
48
- softline,
49
- join(concat([",", line]), path.call(print, "body", 0))
50
- ])
51
- ),
52
- concat([softline, ")"])
53
- ])
54
- );
44
+ const parts = [
45
+ softline,
46
+ join(concat([",", line]), path.call(print, "body", 0))
47
+ ];
48
+
49
+ if (path.getValue().body[0].comma) {
50
+ parts.push(",");
51
+ }
52
+
53
+ return group(concat(["(", indent(concat(parts)), concat([softline, ")"])]));
55
54
  },
56
55
  mrhs: makeList,
57
56
  mrhs_add_star: (path, opts, print) =>
@@ -0,0 +1,60 @@
1
+ const {
2
+ concat,
3
+ group,
4
+ ifBreak,
5
+ indent,
6
+ line,
7
+ join,
8
+ softline
9
+ } = require("../prettier");
10
+ const { literal } = require("../utils");
11
+
12
+ const printReturn = (path, opts, print) => {
13
+ let args = path.getValue().body[0].body[0];
14
+ let steps = ["body", 0, "body", 0];
15
+
16
+ if (!args) {
17
+ return "return";
18
+ }
19
+
20
+ // If the body of the return contains parens, then just skip directly to the
21
+ // content of the parens so that we can skip printing parens if we don't
22
+ // want them.
23
+ if (args.body[0] && args.body[0].type === "paren") {
24
+ args = args.body[0].body[0];
25
+ steps = steps.concat("body", 0, "body", 0);
26
+ }
27
+
28
+ // If we're returning an array literal that isn't a special array, then we
29
+ // want to grab the arguments so that we can print them out as if they were
30
+ // normal return arguments.
31
+ if (
32
+ args.body[0] &&
33
+ args.body[0].type === "array" &&
34
+ ["args", "args_add_star"].includes(args.body[0].body[0].type)
35
+ ) {
36
+ steps = steps.concat("body", 0, "body", 0);
37
+ }
38
+
39
+ // Now that we've established which actual node is the arguments to return,
40
+ // we grab it out of the path by diving down the steps that we've set up.
41
+ const parts = path.call.apply(path, [print].concat(steps));
42
+
43
+ // If we got the value straight out of the parens, then `parts` would only
44
+ // be a singular doc as opposed to an array.
45
+ const value = Array.isArray(parts) ? join(concat([",", line]), parts) : parts;
46
+
47
+ return group(
48
+ concat([
49
+ "return",
50
+ ifBreak(parts.length > 1 ? " [" : "(", " "),
51
+ indent(concat([softline, value])),
52
+ concat([softline, ifBreak(parts.length > 1 ? "]" : ")", "")])
53
+ ])
54
+ );
55
+ };
56
+
57
+ module.exports = {
58
+ return: printReturn,
59
+ return0: literal("return")
60
+ };
@@ -7,7 +7,6 @@ const {
7
7
  join,
8
8
  line,
9
9
  literalline,
10
- markAsRoot,
11
10
  softline,
12
11
  trim
13
12
  } = require("../prettier");
@@ -66,9 +65,7 @@ module.exports = {
66
65
  );
67
66
  },
68
67
  program: (path, opts, print) =>
69
- markAsRoot(
70
- concat([join(literalline, path.map(print, "body")), literalline])
71
- ),
68
+ concat([join(hardline, path.map(print, "body")), hardline]),
72
69
  stmts: (path, opts, print) => {
73
70
  const stmts = path.getValue().body;
74
71
  const parts = [];
@@ -4,7 +4,8 @@ const {
4
4
  hardline,
5
5
  indent,
6
6
  literalline,
7
- softline
7
+ softline,
8
+ join
8
9
  } = require("../prettier");
9
10
  const { concatBody, empty, makeList, prefix, surround } = require("../utils");
10
11
  const escapePattern = require("../escapePattern");
@@ -38,13 +39,14 @@ const getStringQuote = (string, preferSingleQuotes) => {
38
39
 
39
40
  const quotePattern = new RegExp("\\\\([\\s\\S])|(['\"])", "g");
40
41
 
41
- const makeString = (content, enclosingQuote) => {
42
+ const makeString = (content, enclosingQuote, originalQuote) => {
43
+ const replaceOther = ["'", '"'].includes(originalQuote);
42
44
  const otherQuote = enclosingQuote === '"' ? "'" : '"';
43
45
 
44
46
  // Escape and unescape single and double quotes as needed to be able to
45
47
  // enclose `content` with `enclosingQuote`.
46
48
  return content.replace(quotePattern, (match, escaped, quote) => {
47
- if (escaped === otherQuote) {
49
+ if (replaceOther && escaped === otherQuote) {
48
50
  return escaped;
49
51
  }
50
52
 
@@ -60,6 +62,53 @@ const makeString = (content, enclosingQuote) => {
60
62
  });
61
63
  };
62
64
 
65
+ // The `parts` argument that comes into this function is an array of either
66
+ // printed embedded expressions or plain strings (that themselves can contain
67
+ // newlines). What we want to return is an array where each element represents a
68
+ // line in the original text. So we end up tracking every line as we go and
69
+ // pushing onto a `currentLine` variable, then when we hit a new line we push
70
+ // the current line onto the `lines` variable until we've used up every part.
71
+ const makeHeredocLines = parts => {
72
+ let lines = [];
73
+ let currentLine = [];
74
+
75
+ parts.forEach(part => {
76
+ if (part.type === "group" || !part.includes("\n")) {
77
+ // In this case we've either hit an embedded expression or the piece of
78
+ // the current line that we're looking at is in the middle of two of them,
79
+ // so we just push onto the current line and continue on.
80
+ currentLine.push(part);
81
+ return;
82
+ }
83
+
84
+ let splits = part.split("\n");
85
+ if (splits[splits.length - 1] === "") {
86
+ // If a line ends with a newline, then we end up with an empty string at
87
+ // the end of the splits, we just pop it off here since we'll handle the
88
+ // newlines later.
89
+ splits = splits.slice(0, -1);
90
+ }
91
+
92
+ if (currentLine.length > 0) {
93
+ lines.push(concat(currentLine.concat(splits[0])));
94
+ currentLine = [];
95
+ } else {
96
+ lines.push(splits[0]);
97
+ }
98
+
99
+ if (splits.length > 1) {
100
+ lines = lines.concat(splits.slice(1, -1));
101
+ currentLine.push(splits[splits.length - 1]);
102
+ }
103
+ });
104
+
105
+ if (currentLine.length > 0) {
106
+ lines = lines.concat(currentLine);
107
+ }
108
+
109
+ return lines;
110
+ };
111
+
63
112
  module.exports = {
64
113
  "@CHAR": (path, { preferSingleQuotes }, _print) => {
65
114
  const { body } = path.getValue();
@@ -78,10 +127,13 @@ module.exports = {
78
127
  },
79
128
  heredoc: (path, opts, print) => {
80
129
  const { beging, ending } = path.getValue();
130
+ const lines = makeHeredocLines(path.map(print, "body"));
81
131
 
82
132
  return concat([
83
133
  beging,
84
- concat([literalline].concat(path.map(print, "body"))),
134
+ literalline,
135
+ join(literalline, lines),
136
+ literalline,
85
137
  ending
86
138
  ]);
87
139
  },
@@ -130,7 +182,7 @@ module.exports = {
130
182
  string.body.forEach((part, index) => {
131
183
  if (part.type === "@tstring_content") {
132
184
  // In this case, the part of the string is just regular string content
133
- parts.push(makeString(part.body, quote));
185
+ parts.push(makeString(part.body, quote, string.quote));
134
186
  } else {
135
187
  // In this case, the part of the string is an embedded expression
136
188
  parts.push(path.call(print, "body", 0, "body", index));
@@ -14,11 +14,12 @@ require 'json' unless defined?(JSON)
14
14
  require 'ripper'
15
15
 
16
16
  class RipperJS < Ripper
17
- attr_reader :lines, :__end__
17
+ attr_reader :source, :lines, :__end__
18
18
 
19
19
  def initialize(source, *args)
20
20
  super(source, *args)
21
21
 
22
+ @source = source
22
23
  @lines = source.split("\n")
23
24
  @__end__ = nil
24
25
  end
@@ -750,6 +751,31 @@ class RipperJS < Ripper
750
751
  sexp.merge!(type: :lambda, body: [params, stmts])
751
752
  end
752
753
  end
754
+
755
+ # We need to track for `mlhs_paren` and `massign` nodes whether or not
756
+ # there was an extra comma at the end of the expression. For some reason
757
+ # it's not showing up in the AST in an obvious way. In this case we're
758
+ # just simplifying everything by adding an additional field to `mlhs`
759
+ # nodes called `comma` that indicates whether or not there was an extra.
760
+ def on_mlhs_paren(body)
761
+ super.tap do |node|
762
+ next unless body[:type] == :mlhs
763
+
764
+ ending = source.rindex(')', char_pos)
765
+ buffer = source[(node[:char_start] + 1)...ending]
766
+
767
+ body[:comma] = buffer.strip.end_with?(',')
768
+ end
769
+ end
770
+
771
+ def on_massign(left, right)
772
+ super.tap do
773
+ next unless left[:type] == :mlhs
774
+
775
+ range = left[:char_start]..left[:char_end]
776
+ left[:comma] = source[range].strip.end_with?(',')
777
+ end
778
+ end
753
779
  end
754
780
  )
755
781
  end