prettier 0.15.1 → 0.16.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.
@@ -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