hermes-transform 0.6.0 → 0.9.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.
Files changed (42) hide show
  1. package/ESLINT_LICENCE +19 -0
  2. package/PRETTIER_LICENCE +7 -0
  3. package/dist/detachedNode.js +96 -20
  4. package/dist/detachedNode.js.flow +105 -17
  5. package/dist/generated/TransformCloneSignatures.js.flow +33 -66
  6. package/dist/generated/TransformModifySignatures.js.flow +1113 -0
  7. package/dist/generated/TransformReplaceSignatures.js.flow +19 -40
  8. package/dist/generated/node-types.js +638 -905
  9. package/dist/generated/node-types.js.flow +998 -1256
  10. package/dist/generated/special-case-node-types.js +71 -77
  11. package/dist/generated/special-case-node-types.js.flow +104 -88
  12. package/dist/index.js.flow +2 -1
  13. package/dist/transform/MutationContext.js +2 -2
  14. package/dist/transform/MutationContext.js.flow +3 -2
  15. package/dist/transform/TransformContext.js +56 -43
  16. package/dist/transform/TransformContext.js.flow +154 -95
  17. package/dist/transform/comments/comments.js +133 -28
  18. package/dist/transform/comments/comments.js.flow +125 -28
  19. package/dist/transform/getTransformedAST.js +20 -12
  20. package/dist/transform/getTransformedAST.js.flow +32 -14
  21. package/dist/transform/mutations/{AddLeadingComments.js → AddComments.js} +11 -8
  22. package/dist/transform/mutations/AddComments.js.flow +50 -0
  23. package/dist/transform/mutations/CloneCommentsTo.js +1 -2
  24. package/dist/transform/mutations/CloneCommentsTo.js.flow +1 -2
  25. package/dist/transform/mutations/InsertStatement.js +3 -1
  26. package/dist/transform/mutations/InsertStatement.js.flow +7 -1
  27. package/dist/transform/mutations/RemoveNode.js +10 -3
  28. package/dist/transform/mutations/RemoveNode.js.flow +14 -3
  29. package/dist/transform/mutations/ReplaceNode.js +7 -2
  30. package/dist/transform/mutations/ReplaceNode.js.flow +5 -2
  31. package/dist/transform/mutations/ReplaceStatementWithMany.js.flow +5 -2
  32. package/dist/transform/mutations/utils/arrayUtils.js +14 -0
  33. package/dist/transform/mutations/utils/arrayUtils.js.flow +15 -0
  34. package/dist/transform/transform.js +38 -6
  35. package/dist/transform/transform.js.flow +40 -6
  36. package/dist/traverse/NodeEventGenerator.js.flow +1 -1
  37. package/dist/traverse/traverse.js +27 -3
  38. package/dist/traverse/traverse.js.flow +63 -10
  39. package/package.json +9 -4
  40. package/dist/transform/mutations/AddLeadingComments.js.flow +0 -49
  41. package/dist/transform/mutations/AddTrailingComments.js +0 -40
  42. package/dist/transform/mutations/AddTrailingComments.js.flow +0 -49
@@ -3,14 +3,16 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.CommentPlacement = void 0;
7
+ exports.addComment = addComment;
6
8
  exports.addCommentsToNode = addCommentsToNode;
7
- exports.addLeadingComment = addLeadingComment;
8
- exports.addTrailingComment = addTrailingComment;
9
9
  exports.appendCommentToSource = appendCommentToSource;
10
10
  exports.attachComments = attachComments;
11
11
  exports.cloneComment = cloneComment;
12
12
  exports.cloneCommentWithMarkers = cloneCommentWithMarkers;
13
13
  exports.getCommentsForNode = getCommentsForNode;
14
+ exports.getLeadingCommentsForNode = getLeadingCommentsForNode;
15
+ exports.getTrailingCommentsForNode = getTrailingCommentsForNode;
14
16
  exports.isLeadingComment = isLeadingComment;
15
17
  exports.isTrailingComment = isTrailingComment;
16
18
  exports.moveCommentsToNewNode = moveCommentsToNewNode;
@@ -24,6 +26,8 @@ var _printerEstree = _interopRequireDefault(require("./prettier/language-js/prin
24
26
 
25
27
  var _util = require("./prettier/common/util");
26
28
 
29
+ var _os = require("os");
30
+
27
31
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
28
32
 
29
33
  /**
@@ -38,6 +42,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
38
42
  // $FlowExpectedError[untyped-import]
39
43
  // $FlowExpectedError[untyped-import]
40
44
  // $FlowExpectedError[untyped-import]
45
+ const CommentPlacement = require("flow-enums-runtime").Mirrored(["LEADING_OWN_LINE", "LEADING_INLINE", "TRAILING_OWN_LINE", "TRAILING_INLINE"]);
46
+
47
+ exports.CommentPlacement = CommentPlacement;
48
+
41
49
  function attachComments(comments, ast, text) {
42
50
  (0, _comments.attach)(comments, ast, text, {
43
51
  locStart: _loc.locStart,
@@ -56,13 +64,21 @@ function setCommentsOnNode(node, comments) {
56
64
  node.comments = comments;
57
65
  }
58
66
 
59
- function addCommentsToNode(node, comments) {
67
+ function addCommentsToNode(node, comments, side = 'trailing') {
60
68
  var _node$comments;
61
69
 
62
- // $FlowExpectedError - this property is secretly added by prettier.
63
- node.comments = (_node$comments = node.comments) != null ? _node$comments : []; // $FlowExpectedError
64
-
65
- node.comments.push(...comments);
70
+ // $FlowExpectedError[prop-missing] - this property is secretly added by prettier.
71
+ // $FlowExpectedError[cannot-write]
72
+ // $FlowExpectedError[incompatible-use]
73
+ node.comments = (_node$comments = node.comments) != null ? _node$comments : [];
74
+
75
+ if (side === 'leading') {
76
+ // $FlowExpectedError[incompatible-cast]
77
+ node.comments.unshift(...comments);
78
+ } else {
79
+ // $FlowExpectedError[incompatible-cast]
80
+ node.comments.push(...comments);
81
+ }
66
82
  }
67
83
 
68
84
  function getCommentsForNode(node) {
@@ -82,12 +98,30 @@ function isTrailingComment(comment) {
82
98
  return comment.trailing === true;
83
99
  }
84
100
 
85
- function addLeadingComment(node, comment) {
86
- (0, _util.addLeadingComment)(node, comment);
101
+ function getLeadingCommentsForNode(node) {
102
+ return getCommentsForNode(node).filter(isLeadingComment);
103
+ }
104
+
105
+ function getTrailingCommentsForNode(node) {
106
+ return getCommentsForNode(node).filter(isTrailingComment);
87
107
  }
88
108
 
89
- function addTrailingComment(node, comment) {
90
- (0, _util.addTrailingComment)(node, comment);
109
+ function addComment(node, comment, placement) {
110
+ switch (placement) {
111
+ case CommentPlacement.LEADING_OWN_LINE:
112
+ case CommentPlacement.LEADING_INLINE:
113
+ {
114
+ (0, _util.addLeadingComment)(node, comment);
115
+ break;
116
+ }
117
+
118
+ case CommentPlacement.TRAILING_OWN_LINE:
119
+ case CommentPlacement.TRAILING_INLINE:
120
+ {
121
+ (0, _util.addTrailingComment)(node, comment);
122
+ break;
123
+ }
124
+ }
91
125
  }
92
126
 
93
127
  function cloneComment(comment) {
@@ -112,29 +146,100 @@ function cloneCommentWithMarkers(comment) {
112
146
  };
113
147
  }
114
148
 
115
- function appendCommentToSource(code, comment) {
116
- // prettier slices comments directly from the source code when printing
117
- // https://github.com/prettier/prettier/blob/5f0ee39fa03532c85bd1c35291450fe7ac3667b3/src/language-js/print/comment.js#L15-L20
118
- // this means that we need to have any appended comments directly in the
119
- // source code or else prettier will slice nothing and bork up the transform
120
- let commentText = comment.value;
149
+ function getFirstNewlineIndex(code) {
150
+ return code.search(/\r\n|\n|\r/);
151
+ }
152
+
153
+ function getFirstNonWhitespaceIndex(code) {
154
+ return code.search(/\S/);
155
+ }
156
+
157
+ function appendCommentToSource(code, comment, placement) {
158
+ let newCode = code;
121
159
 
122
160
  switch (comment.type) {
123
161
  case 'Block':
124
- commentText = `/*${commentText}*/`;
125
- break;
162
+ {
163
+ // Prettier decides if a newline is necessary between the comment and its node by looking
164
+ // to see if a newline seperates them in the source text. We can trick prettier into
165
+ // formatting how we want for new comments by placing the range such that a newline
166
+ // will (OWN_LINE) or will not (INLINE) be found when searching from the specified range
167
+ // position.
168
+ switch (placement) {
169
+ case CommentPlacement.LEADING_OWN_LINE:
170
+ case CommentPlacement.TRAILING_OWN_LINE:
171
+ {
172
+ // Since we always want a line break we need to ensure a newline is found when
173
+ // searching out from either side of the comment range.
174
+ let firstNewline = getFirstNewlineIndex(code);
175
+
176
+ if (firstNewline === -1) {
177
+ // No newline in file, lets add one.
178
+ newCode += _os.EOL;
179
+ firstNewline = newCode.length;
180
+ } // Prettier only uses these ranges for detecting whitespace, so this nonsensical
181
+ // range is safe.
182
+ // $FlowExpectedError[cannot-write]
183
+
184
+
185
+ comment.range = [firstNewline + 1, firstNewline];
186
+ break;
187
+ }
188
+
189
+ case CommentPlacement.LEADING_INLINE:
190
+ case CommentPlacement.TRAILING_INLINE:
191
+ {
192
+ // Since we don't want a line break we need to ensure a non whitespace char is
193
+ // always found before a newline when searching out from either side of the
194
+ // comment range.
195
+ let firstNonWhitespace = getFirstNonWhitespaceIndex(code);
196
+
197
+ if (firstNonWhitespace === -1) {
198
+ // No non whitespace chars in file, lets add an identifiable statement for prettier to find.
199
+ newCode += '$FORCE_INLINE_ON_EMPTY_FILE_TOKEN$;';
200
+ firstNonWhitespace = newCode.length;
201
+ break;
202
+ } // $FlowExpectedError[cannot-write]
203
+
204
+
205
+ comment.range = [firstNonWhitespace + 1, firstNonWhitespace];
206
+ break;
207
+ }
208
+ }
209
+
210
+ break;
211
+ }
126
212
 
127
213
  case 'Line':
128
- commentText = `//${commentText}`;
129
- break;
214
+ {
215
+ // For `Line` comments prettier slices comments directly from the source code when printing
216
+ // https://github.com/prettier/prettier/blob/5f0ee39fa03532c85bd1c35291450fe7ac3667b3/src/language-js/print/comment.js#L15-L20
217
+ // this means that we need to have any appended comments directly in the
218
+ // source code or else prettier will slice nothing and bork up the transform
219
+ const commentText = `//${comment.value}`;
220
+ const lastChar = newCode[newCode.length - 1];
221
+
222
+ if (lastChar !== '\n' && lastChar !== '\r') {
223
+ newCode += _os.EOL;
224
+ } // Line comments cannot be inline before a node so we only place trailing Line comments inline.
225
+
226
+
227
+ if (placement === CommentPlacement.TRAILING_INLINE) {
228
+ // Prettier determines an "end of line" comment by walking backwards from
229
+ // the comment start range through the source code to see if it finds a non
230
+ // newline token. In order to trick prettier for new comments we need to
231
+ // insert fake source code for it to find.
232
+ newCode += '$FORCE_END_OF_LINE_COMMENT_TOKEN$;';
233
+ }
234
+
235
+ const start = newCode.length;
236
+ newCode += commentText;
237
+ const end = newCode.length; // $FlowExpectedError[cannot-write]
238
+
239
+ comment.range = [start, end];
240
+ break;
241
+ }
130
242
  }
131
243
 
132
- let newCode = code;
133
- newCode += '\n';
134
- const start = newCode.length;
135
- newCode += commentText;
136
- const end = newCode.length; // $FlowExpectedError[cannot-write]
137
-
138
- comment.range = [start, end];
139
244
  return newCode;
140
245
  }
@@ -23,9 +23,17 @@ import {
23
23
  // $FlowExpectedError[untyped-import]
24
24
  addTrailingComment as untypedAddTrailingComment,
25
25
  } from './prettier/common/util';
26
+ import {EOL} from 'os';
26
27
 
27
28
  export type Options = $ReadOnly<{}>;
28
29
 
30
+ export enum CommentPlacement {
31
+ LEADING_OWN_LINE,
32
+ LEADING_INLINE,
33
+ TRAILING_OWN_LINE,
34
+ TRAILING_INLINE,
35
+ }
36
+
29
37
  export function attachComments(
30
38
  comments: $ReadOnlyArray<Comment>,
31
39
  ast: Program,
@@ -57,11 +65,19 @@ export function setCommentsOnNode(
57
65
  export function addCommentsToNode(
58
66
  node: ESNode | DetachedNode<ESNode>,
59
67
  comments: $ReadOnlyArray<Comment>,
68
+ side?: 'leading' | 'trailing' = 'trailing',
60
69
  ): void {
61
- // $FlowExpectedError - this property is secretly added by prettier.
70
+ // $FlowExpectedError[prop-missing] - this property is secretly added by prettier.
71
+ // $FlowExpectedError[cannot-write]
72
+ // $FlowExpectedError[incompatible-use]
62
73
  node.comments = node.comments ?? [];
63
- // $FlowExpectedError
64
- (node.comments: Array<Comment>).push(...comments);
74
+ if (side === 'leading') {
75
+ // $FlowExpectedError[incompatible-cast]
76
+ (node.comments: Array<Comment>).unshift(...comments);
77
+ } else {
78
+ // $FlowExpectedError[incompatible-cast]
79
+ (node.comments: Array<Comment>).push(...comments);
80
+ }
65
81
  }
66
82
 
67
83
  export function getCommentsForNode(
@@ -80,18 +96,35 @@ export function isTrailingComment(comment: Comment): boolean {
80
96
  return comment.trailing === true;
81
97
  }
82
98
 
83
- export function addLeadingComment(
99
+ export function getLeadingCommentsForNode(
84
100
  node: ESNode | DetachedNode<ESNode>,
85
- comment: Comment,
86
- ): void {
87
- untypedAddLeadingComment(node, comment);
101
+ ): $ReadOnlyArray<Comment> {
102
+ return getCommentsForNode(node).filter(isLeadingComment);
103
+ }
104
+
105
+ export function getTrailingCommentsForNode(
106
+ node: ESNode | DetachedNode<ESNode>,
107
+ ): $ReadOnlyArray<Comment> {
108
+ return getCommentsForNode(node).filter(isTrailingComment);
88
109
  }
89
110
 
90
- export function addTrailingComment(
111
+ export function addComment(
91
112
  node: ESNode | DetachedNode<ESNode>,
92
113
  comment: Comment,
114
+ placement: CommentPlacement,
93
115
  ): void {
94
- untypedAddTrailingComment(node, comment);
116
+ switch (placement) {
117
+ case CommentPlacement.LEADING_OWN_LINE:
118
+ case CommentPlacement.LEADING_INLINE: {
119
+ untypedAddLeadingComment(node, comment);
120
+ break;
121
+ }
122
+ case CommentPlacement.TRAILING_OWN_LINE:
123
+ case CommentPlacement.TRAILING_INLINE: {
124
+ untypedAddTrailingComment(node, comment);
125
+ break;
126
+ }
127
+ }
95
128
  }
96
129
 
97
130
  export function cloneComment<T: Comment>(comment: T): T {
@@ -116,30 +149,94 @@ export function cloneCommentWithMarkers<T: Comment>(comment: T): T {
116
149
  };
117
150
  }
118
151
 
119
- export function appendCommentToSource(code: string, comment: Comment): string {
120
- // prettier slices comments directly from the source code when printing
121
- // https://github.com/prettier/prettier/blob/5f0ee39fa03532c85bd1c35291450fe7ac3667b3/src/language-js/print/comment.js#L15-L20
122
- // this means that we need to have any appended comments directly in the
123
- // source code or else prettier will slice nothing and bork up the transform
152
+ function getFirstNewlineIndex(code: string): number {
153
+ return code.search(/\r\n|\n|\r/);
154
+ }
155
+
156
+ function getFirstNonWhitespaceIndex(code: string): number {
157
+ return code.search(/\S/);
158
+ }
124
159
 
125
- let commentText = comment.value;
160
+ export function appendCommentToSource(
161
+ code: string,
162
+ comment: Comment,
163
+ placement: CommentPlacement,
164
+ ): string {
165
+ let newCode = code;
126
166
  switch (comment.type) {
127
- case 'Block':
128
- commentText = `/*${commentText}*/`;
167
+ case 'Block': {
168
+ // Prettier decides if a newline is necessary between the comment and its node by looking
169
+ // to see if a newline seperates them in the source text. We can trick prettier into
170
+ // formatting how we want for new comments by placing the range such that a newline
171
+ // will (OWN_LINE) or will not (INLINE) be found when searching from the specified range
172
+ // position.
173
+ switch (placement) {
174
+ case CommentPlacement.LEADING_OWN_LINE:
175
+ case CommentPlacement.TRAILING_OWN_LINE: {
176
+ // Since we always want a line break we need to ensure a newline is found when
177
+ // searching out from either side of the comment range.
178
+ let firstNewline = getFirstNewlineIndex(code);
179
+ if (firstNewline === -1) {
180
+ // No newline in file, lets add one.
181
+ newCode += EOL;
182
+ firstNewline = newCode.length;
183
+ }
184
+ // Prettier only uses these ranges for detecting whitespace, so this nonsensical
185
+ // range is safe.
186
+ // $FlowExpectedError[cannot-write]
187
+ comment.range = [firstNewline + 1, firstNewline];
188
+ break;
189
+ }
190
+ case CommentPlacement.LEADING_INLINE:
191
+ case CommentPlacement.TRAILING_INLINE: {
192
+ // Since we don't want a line break we need to ensure a non whitespace char is
193
+ // always found before a newline when searching out from either side of the
194
+ // comment range.
195
+ let firstNonWhitespace = getFirstNonWhitespaceIndex(code);
196
+ if (firstNonWhitespace === -1) {
197
+ // No non whitespace chars in file, lets add an identifiable statement for prettier to find.
198
+ newCode += '$FORCE_INLINE_ON_EMPTY_FILE_TOKEN$;';
199
+ firstNonWhitespace = newCode.length;
200
+ break;
201
+ }
202
+
203
+ // $FlowExpectedError[cannot-write]
204
+ comment.range = [firstNonWhitespace + 1, firstNonWhitespace];
205
+ break;
206
+ }
207
+ }
129
208
  break;
130
- case 'Line':
131
- commentText = `//${commentText}`;
209
+ }
210
+ case 'Line': {
211
+ // For `Line` comments prettier slices comments directly from the source code when printing
212
+ // https://github.com/prettier/prettier/blob/5f0ee39fa03532c85bd1c35291450fe7ac3667b3/src/language-js/print/comment.js#L15-L20
213
+ // this means that we need to have any appended comments directly in the
214
+ // source code or else prettier will slice nothing and bork up the transform
215
+ const commentText = `//${comment.value}`;
216
+
217
+ const lastChar = newCode[newCode.length - 1];
218
+ if (lastChar !== '\n' && lastChar !== '\r') {
219
+ newCode += EOL;
220
+ }
221
+
222
+ // Line comments cannot be inline before a node so we only place trailing Line comments inline.
223
+ if (placement === CommentPlacement.TRAILING_INLINE) {
224
+ // Prettier determines an "end of line" comment by walking backwards from
225
+ // the comment start range through the source code to see if it finds a non
226
+ // newline token. In order to trick prettier for new comments we need to
227
+ // insert fake source code for it to find.
228
+ newCode += '$FORCE_END_OF_LINE_COMMENT_TOKEN$;';
229
+ }
230
+ const start = newCode.length;
231
+ newCode += commentText;
232
+ const end = newCode.length;
233
+
234
+ // $FlowExpectedError[cannot-write]
235
+ comment.range = [start, end];
236
+
132
237
  break;
238
+ }
133
239
  }
134
240
 
135
- let newCode = code;
136
- newCode += '\n';
137
- const start = newCode.length;
138
- newCode += commentText;
139
- const end = newCode.length;
140
-
141
- // $FlowExpectedError[cannot-write]
142
- comment.range = [start, end];
143
-
144
241
  return newCode;
145
242
  }
@@ -26,9 +26,7 @@ var _TransformContext = require("./TransformContext");
26
26
 
27
27
  var _comments = require("./comments/comments");
28
28
 
29
- var _AddLeadingComments = require("./mutations/AddLeadingComments");
30
-
31
- var _AddTrailingComments = require("./mutations/AddTrailingComments");
29
+ var _AddComments = require("./mutations/AddComments");
32
30
 
33
31
  var _CloneCommentsTo = require("./mutations/CloneCommentsTo");
34
32
 
@@ -55,8 +53,8 @@ function getTransformedAST(code, visitors) {
55
53
 
56
54
  (0, _comments.attachComments)(ast.comments, ast, code); // traverse the AST and colllect the mutations
57
55
 
58
- const transformContext = (0, _TransformContext.getTransformContext)(code);
59
- (0, _traverse.traverseWithContext)(ast, scopeManager, () => transformContext, visitors); // apply the mutations to the AST
56
+ const transformContext = (0, _TransformContext.getTransformContext)();
57
+ (0, _traverse.traverseWithContext)(code, ast, scopeManager, () => transformContext, visitors); // apply the mutations to the AST
60
58
 
61
59
  const mutationContext = new _MutationContext.MutationContext(code);
62
60
  const removeCommentMutations = [];
@@ -96,14 +94,9 @@ function getTransformedAST(code, visitors) {
96
94
  return null;
97
95
  }
98
96
 
99
- case 'addLeadingComments':
100
- {
101
- return (0, _AddLeadingComments.performAddLeadingCommentsMutation)(mutationContext, mutation);
102
- }
103
-
104
- case 'addTrailingComments':
97
+ case 'addComments':
105
98
  {
106
- return (0, _AddTrailingComments.performAddTrailingCommentsMutation)(mutationContext, mutation);
99
+ return (0, _AddComments.performAddCommentsMutation)(mutationContext, mutation);
107
100
  }
108
101
 
109
102
  case 'cloneCommentsTo':
@@ -129,6 +122,21 @@ function getTransformedAST(code, visitors) {
129
122
  if (mutationRoot) {
130
123
  (0, _detachedNode.updateAllParentPointers)(mutationRoot);
131
124
  }
125
+ } // if the very first node in the program is replaced, it will take the docblock with it
126
+ // this is bad as it means we'll lose `@format`, ``, licence, etc.
127
+ // so this hack just makes sure that we keep the docblock
128
+ // note that we do this **BEFORE** the comment mutations in case someone intentionally
129
+ // wants to remove the docblock comment for some weird reason
130
+
131
+
132
+ if (ast.docblock != null && ast.body.length > 0) {
133
+ const firstNode = ast.body[0];
134
+ const docblockComment = ast.docblock.comment;
135
+ const leadingComments = (0, _comments.getLeadingCommentsForNode)(firstNode);
136
+
137
+ if (!leadingComments.includes(docblockComment)) {
138
+ (0, _comments.addCommentsToNode)(firstNode, [docblockComment], 'leading');
139
+ }
132
140
  } // remove the comments
133
141
  // this is done at the end because it requires a complete traversal of the AST
134
142
  // so that we can find relevant node's attachment array
@@ -11,8 +11,7 @@
11
11
  'use strict';
12
12
 
13
13
  import type {ESNode, Program} from 'hermes-estree';
14
- import type {Visitor} from '../traverse/traverse';
15
- import type {TransformContext} from './TransformContext';
14
+ import type {TransformVisitor} from './transform';
16
15
  import type {RemoveCommentMutation} from './mutations/RemoveComment';
17
16
 
18
17
  import {parseForESLint} from 'hermes-eslint';
@@ -20,9 +19,12 @@ import {updateAllParentPointers} from '../detachedNode';
20
19
  import {traverseWithContext} from '../traverse/traverse';
21
20
  import {MutationContext} from './MutationContext';
22
21
  import {getTransformContext} from './TransformContext';
23
- import {attachComments} from './comments/comments';
24
- import {performAddLeadingCommentsMutation} from './mutations/AddLeadingComments';
25
- import {performAddTrailingCommentsMutation} from './mutations/AddTrailingComments';
22
+ import {
23
+ addCommentsToNode,
24
+ attachComments,
25
+ getLeadingCommentsForNode,
26
+ } from './comments/comments';
27
+ import {performAddCommentsMutation} from './mutations/AddComments';
26
28
  import {performCloneCommentsToMutation} from './mutations/CloneCommentsTo';
27
29
  import {performInsertStatementMutation} from './mutations/InsertStatement';
28
30
  import {performRemoveCommentMutations} from './mutations/RemoveComment';
@@ -33,7 +35,7 @@ import {performReplaceStatementWithManyMutation} from './mutations/ReplaceStatem
33
35
 
34
36
  export function getTransformedAST(
35
37
  code: string,
36
- visitors: Visitor<TransformContext>,
38
+ visitors: TransformVisitor,
37
39
  ): {
38
40
  ast: Program,
39
41
  astWasMutated: boolean,
@@ -48,8 +50,14 @@ export function getTransformedAST(
48
50
  attachComments(ast.comments, ast, code);
49
51
 
50
52
  // traverse the AST and colllect the mutations
51
- const transformContext = getTransformContext(code);
52
- traverseWithContext(ast, scopeManager, () => transformContext, visitors);
53
+ const transformContext = getTransformContext();
54
+ traverseWithContext(
55
+ code,
56
+ ast,
57
+ scopeManager,
58
+ () => transformContext,
59
+ visitors,
60
+ );
53
61
 
54
62
  // apply the mutations to the AST
55
63
  const mutationContext = new MutationContext(code);
@@ -88,12 +96,8 @@ export function getTransformedAST(
88
96
  return null;
89
97
  }
90
98
 
91
- case 'addLeadingComments': {
92
- return performAddLeadingCommentsMutation(mutationContext, mutation);
93
- }
94
-
95
- case 'addTrailingComments': {
96
- return performAddTrailingCommentsMutation(mutationContext, mutation);
99
+ case 'addComments': {
100
+ return performAddCommentsMutation(mutationContext, mutation);
97
101
  }
98
102
 
99
103
  case 'cloneCommentsTo': {
@@ -120,6 +124,20 @@ export function getTransformedAST(
120
124
  }
121
125
  }
122
126
 
127
+ // if the very first node in the program is replaced, it will take the docblock with it
128
+ // this is bad as it means we'll lose `@format`, `@flow`, licence, etc.
129
+ // so this hack just makes sure that we keep the docblock
130
+ // note that we do this **BEFORE** the comment mutations in case someone intentionally
131
+ // wants to remove the docblock comment for some weird reason
132
+ if (ast.docblock != null && ast.body.length > 0) {
133
+ const firstNode = ast.body[0];
134
+ const docblockComment = ast.docblock.comment;
135
+ const leadingComments = getLeadingCommentsForNode(firstNode);
136
+ if (!leadingComments.includes(docblockComment)) {
137
+ addCommentsToNode(firstNode, [docblockComment], 'leading');
138
+ }
139
+ }
140
+
123
141
  // remove the comments
124
142
  // this is done at the end because it requires a complete traversal of the AST
125
143
  // so that we can find relevant node's attachment array
@@ -3,8 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.createAddLeadingCommentsMutation = createAddLeadingCommentsMutation;
7
- exports.performAddLeadingCommentsMutation = performAddLeadingCommentsMutation;
6
+ exports.createAddCommentsMutation = createAddCommentsMutation;
7
+ exports.performAddCommentsMutation = performAddCommentsMutation;
8
8
 
9
9
  var _comments = require("../comments/comments");
10
10
 
@@ -17,23 +17,26 @@ var _comments = require("../comments/comments");
17
17
  *
18
18
  * @format
19
19
  */
20
- function createAddLeadingCommentsMutation(node, comments) {
20
+ function createAddCommentsMutation(node, comments) {
21
21
  if (comments.length === 0) {
22
22
  return null;
23
23
  }
24
24
 
25
25
  return {
26
- type: 'addLeadingComments',
26
+ type: 'addComments',
27
27
  comments,
28
28
  node
29
29
  };
30
30
  }
31
31
 
32
- function performAddLeadingCommentsMutation(mutationContext, mutation) {
33
- for (const originalComment of mutation.comments) {
32
+ function performAddCommentsMutation(mutationContext, mutation) {
33
+ for (const {
34
+ comment: originalComment,
35
+ placement
36
+ } of mutation.comments) {
34
37
  const comment = (0, _comments.cloneComment)(originalComment);
35
- mutationContext.appendCommentToSource(comment);
36
- (0, _comments.addLeadingComment)(mutation.node, comment);
38
+ mutationContext.appendCommentToSource(comment, placement);
39
+ (0, _comments.addComment)(mutation.node, comment, placement);
37
40
  }
38
41
 
39
42
  return null;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ */
10
+
11
+ import type {Comment, ESNode} from 'hermes-estree';
12
+ import type {DetachedNode} from '../../detachedNode';
13
+ import type {MutationContext} from '../MutationContext';
14
+ import type {CommentPlacement} from '../comments/comments';
15
+
16
+ import {addComment, cloneComment} from '../comments/comments';
17
+
18
+ export type AddCommentsMutation = $ReadOnly<{
19
+ type: 'addComments',
20
+ comments: $ReadOnlyArray<{comment: Comment, placement: CommentPlacement}>,
21
+ node: ESNode | DetachedNode<ESNode>,
22
+ }>;
23
+
24
+ export function createAddCommentsMutation(
25
+ node: AddCommentsMutation['node'],
26
+ comments: AddCommentsMutation['comments'],
27
+ ): ?AddCommentsMutation {
28
+ if (comments.length === 0) {
29
+ return null;
30
+ }
31
+
32
+ return {
33
+ type: 'addComments',
34
+ comments,
35
+ node,
36
+ };
37
+ }
38
+
39
+ export function performAddCommentsMutation(
40
+ mutationContext: MutationContext,
41
+ mutation: AddCommentsMutation,
42
+ ): null {
43
+ for (const {comment: originalComment, placement} of mutation.comments) {
44
+ const comment = cloneComment(originalComment);
45
+ mutationContext.appendCommentToSource(comment, placement);
46
+ addComment(mutation.node, comment, placement);
47
+ }
48
+
49
+ return null;
50
+ }
@@ -25,12 +25,11 @@ function createCloneCommentsToMutation(target, destination) {
25
25
  };
26
26
  }
27
27
 
28
- function performCloneCommentsToMutation(mutationContext, mutation) {
28
+ function performCloneCommentsToMutation(_mutationContext, mutation) {
29
29
  const newComments = [];
30
30
 
31
31
  for (const originalComment of (0, _comments.getCommentsForNode)(mutation.target)) {
32
32
  const comment = (0, _comments.cloneCommentWithMarkers)(originalComment);
33
- mutationContext.appendCommentToSource(comment);
34
33
  newComments.push(comment);
35
34
  }
36
35