prettier 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -1
  3. data/CONTRIBUTING.md +2 -2
  4. data/README.md +21 -91
  5. data/lib/prettier.rb +2 -2
  6. data/node_modules/prettier/index.js +54 -54
  7. data/package.json +3 -4
  8. data/rubocop.yml +26 -0
  9. data/src/haml/embed.js +87 -0
  10. data/src/haml/nodes/comment.js +27 -0
  11. data/src/haml/nodes/doctype.js +34 -0
  12. data/src/haml/nodes/filter.js +16 -0
  13. data/src/haml/nodes/hamlComment.js +21 -0
  14. data/src/haml/nodes/plain.js +6 -0
  15. data/src/haml/nodes/root.js +8 -0
  16. data/src/haml/nodes/script.js +33 -0
  17. data/src/haml/nodes/silentScript.js +59 -0
  18. data/src/haml/nodes/tag.js +193 -0
  19. data/src/haml/parser.js +33 -0
  20. data/src/haml/parser.rb +141 -0
  21. data/src/haml/printer.js +28 -0
  22. data/src/{ruby.js → plugin.js} +25 -4
  23. data/src/prettier.js +1 -0
  24. data/src/rbs/parser.js +51 -0
  25. data/src/rbs/parser.rb +91 -0
  26. data/src/rbs/printer.js +605 -0
  27. data/src/{embed.js → ruby/embed.js} +6 -2
  28. data/src/{nodes.js → ruby/nodes.js} +0 -0
  29. data/src/{nodes → ruby/nodes}/alias.js +1 -1
  30. data/src/{nodes → ruby/nodes}/aref.js +8 -1
  31. data/src/{nodes → ruby/nodes}/args.js +2 -2
  32. data/src/{nodes → ruby/nodes}/arrays.js +2 -3
  33. data/src/{nodes → ruby/nodes}/assign.js +7 -3
  34. data/src/ruby/nodes/blocks.js +90 -0
  35. data/src/{nodes → ruby/nodes}/calls.js +18 -11
  36. data/src/{nodes → ruby/nodes}/case.js +1 -1
  37. data/src/{nodes → ruby/nodes}/class.js +1 -1
  38. data/src/ruby/nodes/commands.js +131 -0
  39. data/src/{nodes → ruby/nodes}/conditionals.js +3 -3
  40. data/src/{nodes → ruby/nodes}/constants.js +2 -2
  41. data/src/{nodes → ruby/nodes}/flow.js +2 -2
  42. data/src/{nodes → ruby/nodes}/hashes.js +32 -10
  43. data/src/{nodes → ruby/nodes}/heredocs.js +2 -2
  44. data/src/ruby/nodes/hooks.js +34 -0
  45. data/src/{nodes → ruby/nodes}/ints.js +0 -0
  46. data/src/{nodes → ruby/nodes}/lambdas.js +2 -2
  47. data/src/{nodes → ruby/nodes}/loops.js +10 -7
  48. data/src/{nodes → ruby/nodes}/massign.js +8 -1
  49. data/src/{nodes → ruby/nodes}/methods.js +10 -9
  50. data/src/{nodes → ruby/nodes}/operators.js +2 -2
  51. data/src/{nodes → ruby/nodes}/params.js +31 -16
  52. data/src/{nodes → ruby/nodes}/patterns.js +17 -6
  53. data/src/{nodes → ruby/nodes}/regexp.js +2 -2
  54. data/src/{nodes → ruby/nodes}/rescue.js +2 -2
  55. data/src/ruby/nodes/return.js +94 -0
  56. data/src/{nodes → ruby/nodes}/statements.js +6 -9
  57. data/src/{nodes → ruby/nodes}/strings.js +27 -36
  58. data/src/{nodes → ruby/nodes}/super.js +2 -2
  59. data/src/{nodes → ruby/nodes}/undef.js +1 -1
  60. data/src/{parser.js → ruby/parser.js} +4 -3
  61. data/src/{parser.rb → ruby/parser.rb} +450 -501
  62. data/src/{printer.js → ruby/printer.js} +33 -1
  63. data/src/{toProc.js → ruby/toProc.js} +4 -8
  64. data/src/utils.js +10 -93
  65. data/src/utils/containsAssignment.js +11 -0
  66. data/src/utils/getTrailingComma.js +5 -0
  67. data/src/utils/hasAncestor.js +17 -0
  68. data/src/utils/literal.js +7 -0
  69. data/src/utils/makeCall.js +14 -0
  70. data/src/utils/noIndent.js +11 -0
  71. data/src/utils/skipAssignIndent.js +10 -0
  72. metadata +65 -41
  73. data/src/nodes/blocks.js +0 -85
  74. data/src/nodes/commands.js +0 -91
  75. data/src/nodes/hooks.js +0 -44
  76. data/src/nodes/return.js +0 -72
@@ -0,0 +1,33 @@
1
+ const { spawnSync } = require("child_process");
2
+ const path = require("path");
3
+
4
+ const parser = path.join(__dirname, "./parser.rb");
5
+
6
+ const parse = (text, _parsers, _opts) => {
7
+ const child = spawnSync("ruby", [parser], { input: text });
8
+
9
+ const error = child.stderr.toString();
10
+ if (error) {
11
+ throw new Error(error);
12
+ }
13
+
14
+ const response = child.stdout.toString();
15
+ return JSON.parse(response);
16
+ };
17
+
18
+ const pragmaPattern = /^\s*-#\s*@(prettier|format)/;
19
+ const hasPragma = (text) => pragmaPattern.test(text);
20
+
21
+ // These functions are just placeholders until we can actually perform this
22
+ // properly. The functions are necessary otherwise the format with cursor
23
+ // functions break.
24
+ const locStart = (_node) => 0;
25
+ const locEnd = (_node) => 0;
26
+
27
+ module.exports = {
28
+ parse,
29
+ astFormat: "haml",
30
+ hasPragma,
31
+ locStart,
32
+ locEnd
33
+ };
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup' if ENV['CI']
4
+ require 'haml'
5
+ require 'json'
6
+ require 'ripper'
7
+
8
+ class Haml::Parser::ParseNode
9
+ class DeepAttributeParser
10
+ def parse(string)
11
+ Haml::AttributeParser.available? ? parse_value(string) : string
12
+ end
13
+
14
+ private
15
+
16
+ def literal(string, level)
17
+ level == 0 ? string : "&#{string}"
18
+ end
19
+
20
+ def parse_value(string, level = 0)
21
+ response = Ripper.sexp(string)
22
+ return literal(string, level) unless response
23
+
24
+ case response[1][0][0]
25
+ when :hash
26
+ hash = Haml::AttributeParser.parse(string)
27
+
28
+ if hash
29
+ # Explicitly not using Enumerable#to_h here to support Ruby 2.5
30
+ hash.each_with_object({}) do |(key, value), response|
31
+ response[key] = parse_value(value, level + 1)
32
+ end
33
+ else
34
+ literal(string, level)
35
+ end
36
+ when :string_literal
37
+ string[1...-1]
38
+ else
39
+ literal(string, level)
40
+ end
41
+ end
42
+ end
43
+
44
+ ESCAPE = /Haml::Helpers.html_escape\(\((.+)\)\)/.freeze
45
+
46
+ # If a node comes in as the plain type but starts with one of the special
47
+ # characters that haml parses, then we need to escape it with a \ when
48
+ # printing. So here we make a regexp pattern to check if the node needs to be
49
+ # escaped.
50
+ special_chars =
51
+ Haml::Parser::SPECIAL_CHARACTERS.map { |char| Regexp.escape(char) }
52
+
53
+ SPECIAL_START = /\A(?:#{special_chars.join('|')})/
54
+
55
+ def as_json
56
+ case type
57
+ when :comment, :doctype, :silent_script
58
+ to_h.tap do |json|
59
+ json.delete(:parent)
60
+ json[:children] = children.map(&:as_json)
61
+ end
62
+ when :filter, :haml_comment
63
+ to_h.tap { |json| json.delete(:parent) }
64
+ when :plain
65
+ to_h.tap do |json|
66
+ json.delete(:parent)
67
+ json[:children] = children.map(&:as_json)
68
+
69
+ text = json[:value][:text]
70
+ json[:value][:text] = "\\#{text}" if text.match?(SPECIAL_START)
71
+ end
72
+ when :root
73
+ to_h.tap { |json| json[:children] = children.map(&:as_json) }
74
+ when :script
75
+ to_h.tap do |json|
76
+ json.delete(:parent)
77
+ json[:children] = children.map(&:as_json)
78
+
79
+ if json[:value][:text].match?(ESCAPE)
80
+ json[:value][:text].gsub!(ESCAPE) { $1 }
81
+ json[:value].merge!(escape_html: 'escape_html', interpolate: true)
82
+ end
83
+ end
84
+ when :tag
85
+ to_h.tap do |json|
86
+ json.delete(:parent)
87
+
88
+ # For some reason this is actually using a symbol to represent a null
89
+ # object ref instead of nil itself, so just replacing it here for
90
+ # simplicity in the printer
91
+ json[:value][:object_ref] = nil if json[:value][:object_ref] == :nil
92
+
93
+ # Get a reference to the dynamic attributes hash
94
+ dynamic_attributes = value[:dynamic_attributes].to_h
95
+
96
+ # If we have any in the old style, then we're going to pass it through
97
+ # the deep attribute parser filter.
98
+ if dynamic_attributes[:old]
99
+ dynamic_attributes[:old] =
100
+ DeepAttributeParser.new.parse(dynamic_attributes[:old])
101
+ end
102
+
103
+ json.merge!(
104
+ children: children.map(&:as_json),
105
+ value: value.merge(dynamic_attributes: dynamic_attributes)
106
+ )
107
+ end
108
+ else
109
+ raise ArgumentError, "Unsupported type: #{type}"
110
+ end
111
+ end
112
+ end
113
+
114
+ module Prettier
115
+ class HAMLParser
116
+ def self.parse(source)
117
+ Haml::Parser.new({}).call(source).as_json
118
+ rescue StandardError
119
+ false
120
+ end
121
+ end
122
+ end
123
+
124
+ # If this is the main file we're executing, then most likely this is being
125
+ # executed from the haml.js spawn. In that case, read the ruby source from
126
+ # stdin and report back the AST over stdout.
127
+ if $0 == __FILE__
128
+ response = Prettier::HAMLParser.parse($stdin.read)
129
+
130
+ if !response
131
+ warn(
132
+ '@prettier/plugin-ruby encountered an error when attempting to parse ' \
133
+ 'the HAML source. This usually means there was a syntax error in the ' \
134
+ 'file in question.'
135
+ )
136
+
137
+ exit 1
138
+ end
139
+
140
+ puts JSON.fast_generate(response)
141
+ end
@@ -0,0 +1,28 @@
1
+ const embed = require("./embed");
2
+ const nodes = {
3
+ comment: require("./nodes/comment"),
4
+ doctype: require("./nodes/doctype"),
5
+ filter: require("./nodes/filter"),
6
+ haml_comment: require("./nodes/hamlComment"),
7
+ plain: require("./nodes/plain"),
8
+ root: require("./nodes/root"),
9
+ script: require("./nodes/script"),
10
+ silent_script: require("./nodes/silentScript"),
11
+ tag: require("./nodes/tag")
12
+ };
13
+
14
+ const genericPrint = (path, opts, print) => {
15
+ const { type } = path.getValue();
16
+
17
+ /* istanbul ignore next */
18
+ if (!(type in nodes)) {
19
+ throw new Error(`Unsupported node encountered: ${type}`);
20
+ }
21
+
22
+ return nodes[type](path, opts, print);
23
+ };
24
+
25
+ module.exports = {
26
+ embed,
27
+ print: genericPrint
28
+ };
@@ -1,5 +1,11 @@
1
- const printer = require("./printer");
2
- const parser = require("./parser");
1
+ const rubyPrinter = require("./ruby/printer");
2
+ const rubyParser = require("./ruby/parser");
3
+
4
+ const rbsPrinter = require("./rbs/printer");
5
+ const rbsParser = require("./rbs/parser");
6
+
7
+ const hamlPrinter = require("./haml/printer");
8
+ const hamlParser = require("./haml/parser");
3
9
 
4
10
  /*
5
11
  * metadata mostly pulled from linguist and rubocop:
@@ -67,13 +73,28 @@ module.exports = {
67
73
  interpreters: ["jruby", "macruby", "rake", "rbx", "ruby"],
68
74
  linguistLanguageId: 326,
69
75
  vscodeLanguageIds: ["ruby"]
76
+ },
77
+ {
78
+ name: "RBS",
79
+ parsers: ["rbs"],
80
+ extensions: [".rbs"]
81
+ },
82
+ {
83
+ name: "HAML",
84
+ parsers: ["haml"],
85
+ extensions: [".haml"],
86
+ vscodeLanguageIds: ["haml"]
70
87
  }
71
88
  ],
72
89
  parsers: {
73
- ruby: parser
90
+ ruby: rubyParser,
91
+ rbs: rbsParser,
92
+ haml: hamlParser
74
93
  },
75
94
  printers: {
76
- ruby: printer
95
+ ruby: rubyPrinter,
96
+ rbs: rbsPrinter,
97
+ haml: hamlPrinter
77
98
  },
78
99
  options: {
79
100
  rubyArrayLiteral: {
@@ -1,6 +1,7 @@
1
1
  // If `RBPRETTIER` is set, then this is being run from the `Prettier::run` ruby
2
2
  // method. In that case, we need to pull `prettier` from the node_modules
3
3
  // directly, as it's been shipped with the gem.
4
+ /* istanbul ignore next */
4
5
  const source = process.env.RBPRETTIER ? "../node_modules/prettier" : "prettier";
5
6
 
6
7
  const prettier = require(source);
@@ -0,0 +1,51 @@
1
+ const { spawnSync } = require("child_process");
2
+ const path = require("path");
3
+
4
+ // This function is responsible for taking an input string of text and returning
5
+ // to prettier a JavaScript object that is the equivalent AST that represents
6
+ // the code stored in that string. We accomplish this by spawning a new Ruby
7
+ // process of parser.rb and reading JSON off STDOUT.
8
+ function parse(text, _parsers, _opts) {
9
+ const child = spawnSync("ruby", [path.join(__dirname, "./parser.rb")], {
10
+ input: text,
11
+ maxBuffer: 15 * 1024 * 1024 // 15MB
12
+ });
13
+
14
+ const error = child.stderr.toString();
15
+ if (error) {
16
+ throw new Error(error);
17
+ }
18
+
19
+ const response = child.stdout.toString();
20
+ return JSON.parse(response);
21
+ }
22
+
23
+ const pragmaPattern = /#\s*@(prettier|format)/;
24
+
25
+ // This function handles checking whether or not the source string has the
26
+ // pragma for prettier. This is an optional workflow for incremental adoption.
27
+ function hasPragma(text) {
28
+ return pragmaPattern.test(text);
29
+ }
30
+
31
+ // This function is critical for comments and cursor support, and is responsible
32
+ // for returning the index of the character within the source string that is the
33
+ // beginning of the given node.
34
+ function locStart(node) {
35
+ return (node.location || node.type.location).start_pos;
36
+ }
37
+
38
+ // This function is critical for comments and cursor support, and is responsible
39
+ // for returning the index of the character within the source string that is the
40
+ // ending of the given node.
41
+ function locEnd(node) {
42
+ return (node.location || node.type.location).end_pos;
43
+ }
44
+
45
+ module.exports = {
46
+ parse,
47
+ astFormat: "rbs",
48
+ hasPragma,
49
+ locStart,
50
+ locEnd
51
+ };
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup' if ENV['CI']
4
+ require 'json'
5
+ require 'rbs'
6
+
7
+ # Monkey-patch this so that we can get the character positions.
8
+ class RBS::Location
9
+ def to_json(*args)
10
+ {
11
+ start: { line: start_line, column: start_column },
12
+ end: { line: end_line, column: end_column },
13
+ start_pos: start_pos,
14
+ end_pos: end_pos
15
+ }.to_json(*args)
16
+ end
17
+ end
18
+
19
+ class RBS::Types::Function::Param
20
+ def to_json(*a)
21
+ escaped = name && /\A#{RBS::Parser::KEYWORDS_RE}\z/.match?(name)
22
+ { type: type, name: name, escaped: escaped }.to_json(*a)
23
+ end
24
+ end
25
+
26
+ # Monkey-patch this so that we get the name field in the serialized JSON, as
27
+ # well as information about whether or not we need to escape it.
28
+ class RBS::AST::Members::MethodDefinition
29
+ def to_json(*a)
30
+ {
31
+ member: :method_definition,
32
+ name: name,
33
+ kind: kind,
34
+ types: types,
35
+ annotations: annotations,
36
+ location: location,
37
+ comment: comment,
38
+ overload: overload
39
+ }.to_json(*a)
40
+ end
41
+ end
42
+
43
+ # Monkey-patch this so that we get the information we need about how to join the
44
+ # key-value pairs of the record.
45
+ class RBS::Types::Record
46
+ def to_json(*a)
47
+ fields_extra = {}
48
+
49
+ # Explicitly not using Enumerable#to_h here to support Ruby 2.5
50
+ fields.each do |key, type|
51
+ if key.is_a?(Symbol) && key.match?(/\A[A-Za-z_][A-Za-z_]*\z/) &&
52
+ !key.match?(RBS::Parser::KEYWORDS_RE)
53
+ fields_extra[key] = { type: type, joiner: :label }
54
+ else
55
+ fields_extra[key.inspect] = { type: type, joiner: :rocket }
56
+ end
57
+ end
58
+
59
+ { class: :record, fields: fields_extra, location: location }.to_json(*a)
60
+ end
61
+ end
62
+
63
+ module Prettier
64
+ class RBSParser
65
+ def self.parse(text)
66
+ { declarations: RBS::Parser.parse_signature(text) }
67
+ rescue StandardError
68
+ false
69
+ end
70
+ end
71
+ end
72
+
73
+ # If this is the main file we're executing, then most likely this is being
74
+ # executed from the parser.js spawn. In that case, read the rbs source from
75
+ # stdin and report back the AST over stdout.
76
+
77
+ if $0 == __FILE__
78
+ response = Prettier::RBSParser.parse($stdin.read)
79
+
80
+ if !response
81
+ warn(
82
+ '@prettier/plugin-ruby encountered an error when attempting to parse ' \
83
+ 'the RBS source. This usually means there was a syntax error in the ' \
84
+ 'file in question.'
85
+ )
86
+
87
+ exit 1
88
+ end
89
+
90
+ puts JSON.fast_generate(response)
91
+ end
@@ -0,0 +1,605 @@
1
+ const {
2
+ concat,
3
+ group,
4
+ hardline,
5
+ indent,
6
+ makeString,
7
+ join,
8
+ line,
9
+ softline
10
+ } = require("../prettier");
11
+
12
+ // For some lists of entities in the AST, the parser returns them as an unsorted
13
+ // object (presumably because Ruby hashes have implicit ordering). We do not
14
+ // have that in JavaScript, so here we sort each object by its position in the
15
+ // source string.
16
+ function getSortedKeys(object) {
17
+ return Object.keys(object).sort(
18
+ (left, right) =>
19
+ object[left].type.location.start_pos -
20
+ object[right].type.location.start_pos
21
+ );
22
+ }
23
+
24
+ // In some cases, we want to just defer to whatever was in the source.
25
+ function getSource(node, opts) {
26
+ return opts.originalText.slice(
27
+ node.location.start_pos,
28
+ node.location.end_pos
29
+ );
30
+ }
31
+
32
+ // This is the generic node print function, used to convert any node in the AST
33
+ // into its equivalent Doc representation.
34
+ function printNode(path, opts, print) {
35
+ const node = path.getValue();
36
+ let doc = null;
37
+
38
+ if (node.declarations) {
39
+ return printRoot();
40
+ }
41
+
42
+ /* istanbul ignore else */
43
+ if (node.declaration) {
44
+ switch (node.declaration) {
45
+ case "alias":
46
+ doc = printTypeAlias();
47
+ break;
48
+ case "class":
49
+ doc = printClass();
50
+ break;
51
+ case "constant":
52
+ case "global":
53
+ doc = printConstant();
54
+ break;
55
+ case "interface":
56
+ doc = printInterface();
57
+ break;
58
+ case "module":
59
+ doc = printModule();
60
+ break;
61
+ /* istanbul ignore next */
62
+ default:
63
+ throw new Error(`unknown declaration: ${node.declaration}`);
64
+ }
65
+ } else if (node.member) {
66
+ switch (node.member) {
67
+ case "alias":
68
+ doc = printAlias();
69
+ break;
70
+ case "attr_accessor":
71
+ case "attr_reader":
72
+ case "attr_writer":
73
+ doc = printAttr();
74
+ break;
75
+ case "class_variable":
76
+ case "instance_variable":
77
+ doc = printVariable();
78
+ break;
79
+ case "class_instance_variable":
80
+ doc = concat(["self.", printVariable()]);
81
+ break;
82
+ case "include":
83
+ case "extend":
84
+ case "prepend":
85
+ doc = printMixin();
86
+ break;
87
+ case "public":
88
+ case "private":
89
+ doc = node.member;
90
+ break;
91
+ case "method_definition":
92
+ doc = printMethodDefinition();
93
+ break;
94
+ /* istanbul ignore next */
95
+ default:
96
+ throw new Error(`unknown member: ${node.member}`);
97
+ }
98
+ } else {
99
+ const ast = JSON.stringify(node, null, 2);
100
+ throw new Error(`Unsupported node encountered:\n${ast}`);
101
+ }
102
+
103
+ // Certain nodes can't have annotations at all
104
+ if (node.annotations && node.annotations.length > 0) {
105
+ doc = concat([printAnnotations(), hardline, doc]);
106
+ }
107
+
108
+ if (node.comment) {
109
+ doc = concat([printComment(), hardline, doc]);
110
+ }
111
+
112
+ return doc;
113
+
114
+ // Prints out a string in the source, which looks like:
115
+ // 'foo'
116
+ function printString(node) {
117
+ // We're going to go straight to the source here, as if we don't then we're
118
+ // going to end up with the result of String#inspect, which does weird
119
+ // things to escape sequences.
120
+ const value = getSource(node, opts);
121
+
122
+ // Get the quote that was used in the source and the quote that we want to
123
+ // be using.
124
+ const originalQuote = value[0];
125
+ const preferredQuote = opts.rubySingleQuote ? "'" : '"';
126
+
127
+ // Determine if we're allowed to change the quote based on whether or not
128
+ // there is an escape sequence in the source string.
129
+ const quote = node.literal.includes("\\") ? originalQuote : preferredQuote;
130
+
131
+ return makeString(value.slice(1, -1), quote, false);
132
+ }
133
+
134
+ // Certain nodes are names with optional arguments attached, as in Array[A].
135
+ // We handle all of that printing centralized here.
136
+ function printNameAndArgs(path) {
137
+ const node = path.getValue();
138
+
139
+ if (node.args.length === 0) {
140
+ return node.name;
141
+ }
142
+
143
+ return group(
144
+ concat([node.name, "[", join(", ", path.map(printType, "args")), "]"])
145
+ );
146
+ }
147
+
148
+ // This is the big function that prints out any individual type, which can
149
+ // look like all kinds of things, listed in the case statement below.
150
+ function printType(path) {
151
+ const node = path.getValue();
152
+
153
+ switch (node.class) {
154
+ case "literal":
155
+ if (node.literal[0] === '"') {
156
+ return printString(node);
157
+ }
158
+ return node.literal;
159
+ case "optional":
160
+ return concat([path.call(printType, "type"), "?"]);
161
+ case "tuple":
162
+ // If we don't have any sub types, we explicitly need the space in between
163
+ // the brackets to not confuse the parser.
164
+ if (node.types.length === 0) {
165
+ return "[ ]";
166
+ }
167
+
168
+ return group(
169
+ concat(["[", join(", ", path.map(printType, "types")), "]"])
170
+ );
171
+ case "union": {
172
+ const doc = group(
173
+ join(concat([line, "| "]), path.map(printType, "types"))
174
+ );
175
+
176
+ const parentType = path.getParentNode().class;
177
+ return parentType === "intersection" ? concat(["(", doc, ")"]) : doc;
178
+ }
179
+ case "intersection":
180
+ return group(join(concat([line, "& "]), path.map(printType, "types")));
181
+ case "class_singleton":
182
+ return concat(["singleton(", node.name, ")"]);
183
+ case "proc":
184
+ return concat(["^", printMethodSignature(path)]);
185
+ case "record": {
186
+ const parts = [];
187
+
188
+ getSortedKeys(node.fields).forEach((field) => {
189
+ const fieldParts = [];
190
+
191
+ if (node.fields[field].joiner === "rocket") {
192
+ fieldParts.push(`${field} => `);
193
+ } else {
194
+ fieldParts.push(`${field}: `);
195
+ }
196
+
197
+ fieldParts.push(path.call(printType, "fields", field, "type"));
198
+ parts.push(concat(fieldParts));
199
+ });
200
+
201
+ return group(
202
+ concat([
203
+ "{",
204
+ indent(concat([line, join(concat([",", line]), parts)])),
205
+ line,
206
+ "}"
207
+ ])
208
+ );
209
+ }
210
+ case "class_instance":
211
+ case "interface":
212
+ return printNameAndArgs(path);
213
+ case "alias":
214
+ case "variable":
215
+ return node.name;
216
+ case "bool":
217
+ case "bot":
218
+ case "class":
219
+ case "instance":
220
+ case "nil":
221
+ case "self":
222
+ case "top":
223
+ case "untyped":
224
+ case "void":
225
+ return node.class;
226
+ /* istanbul ignore next */
227
+ default:
228
+ throw new Error(`unknown type: ${node.class}`);
229
+ }
230
+ }
231
+
232
+ // Prints out the root of the tree, which includes zero or more declarations.
233
+ function printRoot() {
234
+ return concat([
235
+ join(concat([hardline, hardline]), path.map(print, "declarations")),
236
+ hardline
237
+ ]);
238
+ }
239
+
240
+ // Prints out the members of a class, module, or interface.
241
+ function printMembers() {
242
+ let lastLine = null;
243
+ const docs = [];
244
+
245
+ path.each((memberPath) => {
246
+ const memberNode = memberPath.getValue();
247
+
248
+ if (lastLine !== null && memberNode.location.start.line - lastLine >= 2) {
249
+ docs.push(concat([hardline, hardline]));
250
+ } else {
251
+ docs.push(hardline);
252
+ }
253
+
254
+ docs.push(print(memberPath));
255
+ lastLine = memberNode.location.end.line;
256
+ }, "members");
257
+
258
+ return concat(docs);
259
+ }
260
+
261
+ // Prints out a type alias, which is a declaration that looks like:
262
+ // type foo = String
263
+ function printTypeAlias() {
264
+ return group(
265
+ concat([
266
+ "type ",
267
+ node.name,
268
+ " =",
269
+ indent(group(concat([line, path.call(printType, "type")])))
270
+ ])
271
+ );
272
+ }
273
+
274
+ // Prints out the name of a class, interface, or module declaration.
275
+ // Additionally loops through each type parameter if there are any and print
276
+ // them out joined by commas. Checks for validation and variance.
277
+ function printNameAndTypeParams() {
278
+ if (node.type_params.params.length === 0) {
279
+ return node.name;
280
+ }
281
+
282
+ const docs = path.map(
283
+ (paramPath) => {
284
+ const node = paramPath.getValue();
285
+ const parts = [];
286
+
287
+ if (node.skip_validation) {
288
+ parts.push("unchecked");
289
+ }
290
+
291
+ if (node.variance === "covariant") {
292
+ parts.push("out");
293
+ } else if (node.variance === "contravariant") {
294
+ parts.push("in");
295
+ }
296
+
297
+ return join(" ", parts.concat(node.name));
298
+ },
299
+ "type_params",
300
+ "params"
301
+ );
302
+
303
+ return concat([node.name, "[", join(", ", docs), "]"]);
304
+ }
305
+
306
+ // Prints out a class declarations, which looks like:
307
+ // class Foo end
308
+ function printClass() {
309
+ const parts = ["class ", printNameAndTypeParams()];
310
+
311
+ if (node.super_class) {
312
+ parts.push(" < ", path.call(printNameAndArgs, "super_class"));
313
+ }
314
+
315
+ parts.push(indent(printMembers()), hardline, "end");
316
+
317
+ return group(concat(parts));
318
+ }
319
+
320
+ // Prints out a constant or a global declaration, which looks like:
321
+ // Foo: String
322
+ // $foo: String
323
+ function printConstant() {
324
+ return group(concat([node.name, ": ", path.call(printType, "type")]));
325
+ }
326
+
327
+ // Prints out an interface declaration, which looks like:
328
+ // interface _Foo end
329
+ function printInterface() {
330
+ return group(
331
+ concat([
332
+ "interface ",
333
+ printNameAndTypeParams(),
334
+ indent(printMembers()),
335
+ hardline,
336
+ "end"
337
+ ])
338
+ );
339
+ }
340
+
341
+ // Prints out a module declaration, which looks like:
342
+ // module Foo end
343
+ function printModule() {
344
+ const parts = ["module ", printNameAndTypeParams()];
345
+
346
+ if (node.self_types.length > 0) {
347
+ parts.push(" : ", join(", ", path.map(printNameAndArgs, "self_types")));
348
+ }
349
+
350
+ parts.push(indent(printMembers()), hardline, "end");
351
+
352
+ return group(concat(parts));
353
+ }
354
+
355
+ // Prints out an alias within a declaration, which looks like:
356
+ // alias foo bar
357
+ // alias self.foo self.bar
358
+ function printAlias() {
359
+ if (node.kind === "singleton") {
360
+ return concat(["alias self.", node.new_name, " self.", node.old_name]);
361
+ }
362
+
363
+ return concat(["alias ", node.new_name, " ", node.old_name]);
364
+ }
365
+
366
+ // Prints out an attr_* meta method, which looks like:
367
+ // attr_accessor foo
368
+ // attr_reader self.foo()
369
+ // attr_writer self.foo(@bar): String
370
+ function printAttr() {
371
+ const parts = [node.member, " "];
372
+
373
+ if (node.kind === "singleton") {
374
+ parts.push("self.");
375
+ }
376
+
377
+ parts.push(node.name);
378
+
379
+ if (node.ivar_name === false) {
380
+ parts.push("()");
381
+ } else if (node.ivar_name) {
382
+ parts.push("(", node.ivar_name, ")");
383
+ }
384
+
385
+ parts.push(": ", path.call(printType, "type"));
386
+
387
+ return group(concat(parts));
388
+ }
389
+
390
+ // Prints out a variable member, which looks like:
391
+ // @foo: String
392
+ // self.@foo: String
393
+ // @@foo: String
394
+ function printVariable() {
395
+ return group(concat([node.name, ": ", path.call(printType, "type")]));
396
+ }
397
+
398
+ // Prints out a mixin, which looks like:
399
+ // include Foo
400
+ // prepend Foo
401
+ // extend Foo
402
+ function printMixin() {
403
+ return group(concat([node.member, " ", printNameAndArgs(path)]));
404
+ }
405
+
406
+ // Returns an array of printed parameters so that the calling function can
407
+ // join them together in whatever way.
408
+ function printMethodParams(path) {
409
+ const node = path.getValue();
410
+ let parts = [];
411
+
412
+ // required positionals, as in (A)
413
+ parts = parts.concat(path.map(printMethodParam, "required_positionals"));
414
+
415
+ // optional positionals, as in (?A)
416
+ parts = parts.concat(
417
+ path.map(
418
+ (paramPath) => concat(["?", printMethodParam(paramPath)]),
419
+ "optional_positionals"
420
+ )
421
+ );
422
+
423
+ // rest positional, as in (*A)
424
+ if (node.rest_positionals) {
425
+ parts.push(
426
+ concat(["*", path.call(printMethodParam, "rest_positionals")])
427
+ );
428
+ }
429
+
430
+ // trailing positionals are required positionals after a rest
431
+ parts = parts.concat(path.map(printMethodParam, "trailing_positionals"));
432
+
433
+ // required keywords, as in (a: A)
434
+ getSortedKeys(node.required_keywords).forEach((name) => {
435
+ parts.push(
436
+ concat([
437
+ name,
438
+ ": ",
439
+ path.call(printMethodParam, "required_keywords", name)
440
+ ])
441
+ );
442
+ });
443
+
444
+ // optional keywords, as in (?a: A)
445
+ getSortedKeys(node.optional_keywords).forEach((name) => {
446
+ parts.push(
447
+ concat([
448
+ "?",
449
+ name,
450
+ ": ",
451
+ path.call(printMethodParam, "optional_keywords", name)
452
+ ])
453
+ );
454
+ });
455
+
456
+ // rest keyword, as in (**A)
457
+ if (node.rest_keywords) {
458
+ parts.push(concat(["**", path.call(printMethodParam, "rest_keywords")]));
459
+ }
460
+
461
+ return parts;
462
+
463
+ // Prints out a method parameter at a given path. Handles printing out the
464
+ // name if there is one (and whether or not it's escaped).
465
+ function printMethodParam(path) {
466
+ const node = path.getValue();
467
+ const parts = [path.call(printType, "type")];
468
+
469
+ if (node.name) {
470
+ parts.push(" ");
471
+
472
+ if (node.escaped) {
473
+ parts.push("`", node.name, "`");
474
+ } else {
475
+ parts.push(node.name);
476
+ }
477
+ }
478
+
479
+ return concat(parts);
480
+ }
481
+ }
482
+
483
+ // Prints out a specific method signature, which looks like:
484
+ // (T t) -> void
485
+ function printMethodSignature(path) {
486
+ const node = path.getValue();
487
+ const parts = [];
488
+
489
+ // We won't have a type_params key if we're printing a block
490
+ if (node.type_params && node.type_params.length > 0) {
491
+ parts.push("[", join(", ", node.type_params), "] ");
492
+ }
493
+
494
+ let params = path.call(printMethodParams, "type");
495
+
496
+ if (params.length > 0) {
497
+ parts.push(
498
+ "(",
499
+ indent(concat([softline, join(concat([",", line]), params)])),
500
+ softline,
501
+ ") "
502
+ );
503
+ }
504
+
505
+ if (node.block) {
506
+ if (!node.block.required) {
507
+ parts.push("?");
508
+ }
509
+
510
+ parts.push(
511
+ "{",
512
+ indent(concat([line, path.call(printMethodSignature, "block")])),
513
+ line,
514
+ "} "
515
+ );
516
+ }
517
+
518
+ parts.push("-> ", path.call(printType, "type", "return_type"));
519
+
520
+ return group(concat(parts));
521
+ }
522
+
523
+ // Prints out a method definition, which looks like:
524
+ // def t: (T t) -> void
525
+ function printMethodDefinition() {
526
+ let typeDocs = path.map(printMethodSignature, "types");
527
+
528
+ if (node.overload) {
529
+ typeDocs.push("...");
530
+ }
531
+
532
+ if (typeDocs.length === 1) {
533
+ typeDocs = concat([" ", typeDocs[0]]);
534
+ } else {
535
+ typeDocs = indent(
536
+ group(concat([line, join(concat([line, "| "]), typeDocs)]))
537
+ );
538
+ }
539
+
540
+ const parts = ["def "];
541
+
542
+ if (node.kind === "singleton") {
543
+ parts.push("self.");
544
+ } else if (node.kind === "singleton_instance") {
545
+ parts.push("self?.");
546
+ }
547
+
548
+ const escaped = isMethodNameEscaped();
549
+ parts.push(escaped ? `\`${node.name}\`` : node.name, ":", typeDocs);
550
+
551
+ return group(concat(parts));
552
+
553
+ // Determine if a method name is escaped in the original source.
554
+ function isMethodNameEscaped() {
555
+ const pos = node.location.start_pos + 4;
556
+ const name = opts.originalText.slice(pos, pos + 2).trimStart();
557
+
558
+ return name[0] === "`" && name[1] !== ":";
559
+ }
560
+ }
561
+
562
+ // An annotation can be attached to most kinds of nodes, and should be printed
563
+ // using %a{}.
564
+ function printAnnotations() {
565
+ return join(hardline, path.map(printAnnotation, "annotations"));
566
+
567
+ function printAnnotation(path) {
568
+ const node = path.getValue();
569
+
570
+ // If there are already braces inside the annotation, then we're just
571
+ // going to print out the original string to avoid having to escape
572
+ // anything.
573
+ if (/[{}]/.test(node.string)) {
574
+ return getSource(node, opts);
575
+ }
576
+
577
+ return concat(["%a{", node.string, "}"]);
578
+ }
579
+ }
580
+
581
+ // Comments come in as one whole string, so here we split it up into multiple
582
+ // lines and then prefix it with the pound sign.
583
+ function printComment() {
584
+ const lines = node.comment.string.slice(0, -1).split("\n");
585
+
586
+ return join(
587
+ hardline,
588
+ lines.map((segment) => `# ${segment}`)
589
+ );
590
+ }
591
+ }
592
+
593
+ // This is an escape-hatch to ignore nodes in the tree. If you have a comment
594
+ // that includes this pattern, then the entire node will be ignored and just the
595
+ // original source will be printed out.
596
+ function hasPrettierIgnore(path) {
597
+ const node = path.getValue();
598
+
599
+ return node.comment && node.comment.string.includes("prettier-ignore");
600
+ }
601
+
602
+ module.exports = {
603
+ print: printNode,
604
+ hasPrettierIgnore
605
+ };