hermes-transform 0.22.0 → 0.23.1

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.
@@ -157,6 +157,7 @@ import type {
157
157
  OpaqueType,
158
158
  OptionalIndexedAccessType,
159
159
  PrivateIdentifier,
160
+ Program,
160
161
  Property,
161
162
  PropertyDefinition,
162
163
  QualifiedTypeIdentifier,
@@ -339,6 +340,7 @@ import type {
339
340
  OpaqueTypeProps,
340
341
  OptionalIndexedAccessTypeProps,
341
342
  PrivateIdentifierProps,
343
+ ProgramProps,
342
344
  PropertyProps,
343
345
  PropertyDefinitionProps,
344
346
  QualifiedTypeIdentifierProps,
@@ -1467,6 +1469,14 @@ type PrivateIdentifierCloneSignature = ((
1467
1469
  node: ?PrivateIdentifier,
1468
1470
  newProps: Partial<PrivateIdentifierProps>,
1469
1471
  ) => DetachedNode<PrivateIdentifier> | null);
1472
+ type ProgramCloneSignature = ((
1473
+ node: Program,
1474
+ newProps: Partial<ProgramProps>,
1475
+ ) => DetachedNode<Program>) &
1476
+ ((
1477
+ node: ?Program,
1478
+ newProps: Partial<ProgramProps>,
1479
+ ) => DetachedNode<Program> | null);
1470
1480
  type PropertyCloneSignature = ((
1471
1481
  node: Property,
1472
1482
  newProps: Partial<PropertyProps>,
@@ -1959,6 +1969,7 @@ export type TransformCloneSignatures = AnyTypeAnnotationCloneSignature &
1959
1969
  OpaqueTypeCloneSignature &
1960
1970
  OptionalIndexedAccessTypeCloneSignature &
1961
1971
  PrivateIdentifierCloneSignature &
1972
+ ProgramCloneSignature &
1962
1973
  PropertyCloneSignature &
1963
1974
  PropertyDefinitionCloneSignature &
1964
1975
  QualifiedTypeIdentifierCloneSignature &
@@ -157,6 +157,7 @@ import type {
157
157
  OpaqueType,
158
158
  OptionalIndexedAccessType,
159
159
  PrivateIdentifier,
160
+ Program,
160
161
  Property,
161
162
  PropertyDefinition,
162
163
  QualifiedTypeIdentifier,
@@ -339,6 +340,7 @@ import type {
339
340
  OpaqueTypeProps,
340
341
  OptionalIndexedAccessTypeProps,
341
342
  PrivateIdentifierProps,
343
+ ProgramProps,
342
344
  PropertyProps,
343
345
  PropertyDefinitionProps,
344
346
  QualifiedTypeIdentifierProps,
@@ -926,6 +928,10 @@ type PrivateIdentifierModifySignature = (
926
928
  node: ?PrivateIdentifier,
927
929
  newProps: Partial<PrivateIdentifierProps>,
928
930
  ) => void;
931
+ type ProgramModifySignature = (
932
+ node: ?Program,
933
+ newProps: Partial<ProgramProps>,
934
+ ) => void;
929
935
  type PropertyModifySignature = (
930
936
  node: ?Property,
931
937
  newProps: Partial<PropertyProps>,
@@ -1241,6 +1247,7 @@ export type TransformModifySignatures = AnyTypeAnnotationModifySignature &
1241
1247
  OpaqueTypeModifySignature &
1242
1248
  OptionalIndexedAccessTypeModifySignature &
1243
1249
  PrivateIdentifierModifySignature &
1250
+ ProgramModifySignature &
1244
1251
  PropertyModifySignature &
1245
1252
  PropertyDefinitionModifySignature &
1246
1253
  QualifiedTypeIdentifierModifySignature &
@@ -89,15 +89,19 @@ function Identifier(props) {
89
89
  });
90
90
  (0, _detachedNode.setParentPointersInDirectChildren)(node);
91
91
  return node;
92
- }
92
+ } // Program has a bunch of stuff that usually you don't want to provide - so we have
93
+ // this manual def to allow us to default some values
94
+
93
95
 
94
96
  function Program(props) {
97
+ var _props$sourceType, _props$tokens, _props$comments;
98
+
95
99
  return (0, _detachedNode.detachedProps)(null, {
96
100
  type: 'Program',
97
- sourceType: props.sourceType,
101
+ sourceType: (_props$sourceType = props.sourceType) != null ? _props$sourceType : 'module',
98
102
  body: props.body.map(n => (0, _detachedNode.asDetachedNode)(n)),
99
- tokens: props.tokens,
100
- comments: props.comments,
103
+ tokens: (_props$tokens = props.tokens) != null ? _props$tokens : [],
104
+ comments: (_props$comments = props.comments) != null ? _props$comments : [],
101
105
  interpreter: props.interpreter != null ? // $FlowFixMe[incompatible-call]
102
106
  (0, _detachedNode.asDetachedNode)({
103
107
  type: 'InterpreterDirective',
@@ -156,23 +156,25 @@ export function Identifier(props: {
156
156
  return node;
157
157
  }
158
158
 
159
+ // Program has a bunch of stuff that usually you don't want to provide - so we have
160
+ // this manual def to allow us to default some values
159
161
  export type ProgramProps = {
160
- +sourceType: ProgramType['sourceType'],
162
+ +sourceType?: ?ProgramType['sourceType'],
161
163
  +body: $ReadOnlyArray<MaybeDetachedNode<ProgramType['body'][number]>>,
162
- +tokens: $ReadOnlyArray<MaybeDetachedNode<TokenType>>,
163
- +comments: $ReadOnlyArray<MaybeDetachedNode<CommentType>>,
164
- +interpreter: null | string,
165
- +docblock: null | DocblockMetadataType,
164
+ +tokens?: ?$ReadOnlyArray<MaybeDetachedNode<TokenType>>,
165
+ +comments?: ?$ReadOnlyArray<MaybeDetachedNode<CommentType>>,
166
+ +interpreter?: ?string,
167
+ +docblock?: ?DocblockMetadataType,
166
168
  };
167
169
  export function Program(props: {
168
170
  ...$ReadOnly<ProgramProps>,
169
171
  }): DetachedNode<ProgramType> {
170
172
  return detachedProps<ProgramType>(null, {
171
173
  type: 'Program',
172
- sourceType: props.sourceType,
174
+ sourceType: props.sourceType ?? 'module',
173
175
  body: props.body.map(n => asDetachedNode(n)),
174
- tokens: props.tokens,
175
- comments: props.comments,
176
+ tokens: props.tokens ?? [],
177
+ comments: props.comments ?? [],
176
178
  interpreter:
177
179
  props.interpreter != null
178
180
  ? // $FlowFixMe[incompatible-call]
@@ -7,6 +7,8 @@ exports.getTransformContext = getTransformContext;
7
7
 
8
8
  var _detachedNode = require("../detachedNode");
9
9
 
10
+ var _hermesParser = require("hermes-parser");
11
+
10
12
  var _comments = require("./comments/comments");
11
13
 
12
14
  var _AddComments = require("./mutations/AddComments");
@@ -25,6 +27,8 @@ var _ReplaceNode = require("./mutations/ReplaceNode");
25
27
 
26
28
  var _ReplaceStatementWithMany = require("./mutations/ReplaceStatementWithMany");
27
29
 
30
+ var _ModifyNodeInPlace = require("./mutations/ModifyNodeInPlace");
31
+
28
32
  /**
29
33
  * Copyright (c) Meta Platforms, Inc. and affiliates.
30
34
  *
@@ -167,15 +171,20 @@ function getTransformContext() {
167
171
  }
168
172
  };
169
173
  const modifyAPIs = {
170
- modifyNodeInPlace: (node, newProps = {}, options) => {
171
- if (node == null) {
172
- return;
174
+ modifyNodeInPlace: (target, newProps) => {
175
+ const detachedProps = {};
176
+
177
+ for (const [key, value] of Object.entries(newProps)) {
178
+ if ((0, _hermesParser.isNode)(value)) {
179
+ // $FlowFixMe[incompatible-type]
180
+ const node = value;
181
+ detachedProps[key] = (0, _detachedNode.asDetachedNode)(node);
182
+ } else {
183
+ detachedProps[key] = value;
184
+ }
173
185
  }
174
186
 
175
- const cloned = (0, _detachedNode.shallowCloneNode)(node, newProps, {
176
- preserveLocation: true
177
- });
178
- replaceAPIs.replaceNode(node, cloned, options);
187
+ pushMutation((0, _ModifyNodeInPlace.createModifyNodeInPlaceMutation)(target, detachedProps));
179
188
  }
180
189
  };
181
190
  return {
@@ -30,12 +30,14 @@ import type {RemoveCommentMutation} from './mutations/RemoveComment';
30
30
  import type {RemoveNodeMutation} from './mutations/RemoveNode';
31
31
  import type {RemoveStatementMutation} from './mutations/RemoveStatement';
32
32
  import type {ReplaceNodeMutation} from './mutations/ReplaceNode';
33
+ import type {ModifyNodeInPlaceMutation} from './mutations/ModifyNodeInPlace';
33
34
  import type {
34
35
  ReplaceStatementWithManyMutation,
35
36
  ReplaceStatementWithManyMutationNodes,
36
37
  } from './mutations/ReplaceStatementWithMany';
37
38
 
38
39
  import {asDetachedNode, deepCloneNode, shallowCloneNode} from '../detachedNode';
40
+ import {isNode} from 'hermes-parser';
39
41
  import {
40
42
  CommentPlacement,
41
43
  getCommentsForNode,
@@ -50,6 +52,7 @@ import {createRemoveNodeMutation} from './mutations/RemoveNode';
50
52
  import {createRemoveStatementMutation} from './mutations/RemoveStatement';
51
53
  import {createReplaceNodeMutation} from './mutations/ReplaceNode';
52
54
  import {createReplaceStatementWithManyMutation} from './mutations/ReplaceStatementWithMany';
55
+ import {createModifyNodeInPlaceMutation} from './mutations/ModifyNodeInPlace';
53
56
 
54
57
  type Mutation = $ReadOnly<
55
58
  | AddCommentsMutation
@@ -59,7 +62,8 @@ type Mutation = $ReadOnly<
59
62
  | RemoveNodeMutation
60
63
  | RemoveStatementMutation
61
64
  | ReplaceNodeMutation
62
- | ReplaceStatementWithManyMutation,
65
+ | ReplaceStatementWithManyMutation
66
+ | ModifyNodeInPlaceMutation,
63
67
  >;
64
68
 
65
69
  type SingleOrArray<+T> = T | $ReadOnlyArray<T>;
@@ -525,21 +529,19 @@ export function getTransformContext(): TransformContextAdditions {
525
529
  }: TransformReplaceAPIs['replaceStatementWithMany']),
526
530
  };
527
531
  const modifyAPIs: TransformModifyAPIs = {
528
- modifyNodeInPlace: ((
529
- node: ?ESNode,
530
- newProps?: $ReadOnly<{...}> = {},
531
- options?: ReplaceNodeOptions,
532
- ): void => {
533
- if (node == null) {
534
- return;
532
+ modifyNodeInPlace: ((target: ESNode, newProps: $ReadOnly<{...}>): void => {
533
+ const detachedProps = {};
534
+ for (const [key, value] of Object.entries(newProps)) {
535
+ if (isNode(value)) {
536
+ // $FlowFixMe[incompatible-type]
537
+ const node: ESNode = value;
538
+ detachedProps[key] = asDetachedNode(node);
539
+ } else {
540
+ detachedProps[key] = value;
541
+ }
535
542
  }
536
543
 
537
- const cloned = shallowCloneNode(node, newProps, {preserveLocation: true});
538
- replaceAPIs.replaceNode(
539
- (node: $FlowFixMe),
540
- (cloned: $FlowFixMe),
541
- options,
542
- );
544
+ pushMutation(createModifyNodeInPlaceMutation(target, detachedProps));
543
545
  }: TransformModifyAPIs['modifyNodeInPlace']),
544
546
  };
545
547
 
@@ -5,19 +5,21 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.CommentPlacement = void 0;
7
7
  exports.addComment = addComment;
8
- exports.addCommentsToNode = addCommentsToNode;
9
8
  exports.appendCommentToSource = appendCommentToSource;
10
9
  exports.attachComments = attachComments;
11
10
  exports.cloneComment = cloneComment;
12
11
  exports.cloneCommentWithMarkers = cloneCommentWithMarkers;
12
+ exports.cloneCommentsToNewNode = cloneCommentsToNewNode;
13
13
  exports.cloneJSDocCommentsToNewNode = cloneJSDocCommentsToNewNode;
14
14
  exports.getCommentsForNode = getCommentsForNode;
15
15
  exports.getLeadingCommentsForNode = getLeadingCommentsForNode;
16
16
  exports.getTrailingCommentsForNode = getTrailingCommentsForNode;
17
+ exports.isAttachedComment = isAttachedComment;
17
18
  exports.isLeadingComment = isLeadingComment;
18
19
  exports.isTrailingComment = isTrailingComment;
19
20
  exports.makeCommentOwnLine = makeCommentOwnLine;
20
21
  exports.moveCommentsToNewNode = moveCommentsToNewNode;
22
+ exports.mutateESTreeASTCommentsForPrettier = mutateESTreeASTCommentsForPrettier;
21
23
  exports.setCommentsOnNode = setCommentsOnNode;
22
24
 
23
25
  var _comments = require("./prettier/main/comments");
@@ -58,11 +60,65 @@ function attachComments(comments, ast, text) {
58
60
  });
59
61
  }
60
62
 
63
+ function mutateESTreeASTCommentsForPrettier(program, text) {
64
+ let code = text; // we need to delete the comments prop or else prettier will do
65
+ // its own attachment pass after the mutation and duplicate the
66
+ // comments on each node, borking the output
67
+ // $FlowExpectedError[cannot-write]
68
+
69
+ delete program.comments; // The docblock comment is never attached to any AST nodes, since its technically
70
+ // attached to the program. However this is specific to our AST and in order for
71
+ // prettier to correctly print it we need to attach it to the first node in the
72
+ // program body.
73
+
74
+ if (program.docblock != null && program.docblock.comment != null) {
75
+ const docblockComment = program.docblock.comment;
76
+ const isDocblockCommentNew = !isAttachedComment(docblockComment);
77
+
78
+ if (isDocblockCommentNew) {
79
+ // $FlowExpectedError[prop-missing]
80
+ docblockComment.printed = false; // $FlowExpectedError[prop-missing]
81
+
82
+ docblockComment.leading = true; // $FlowExpectedError[prop-missing]
83
+
84
+ docblockComment.trailing = false;
85
+ } // If we have a first node in the program body, attache the comment to that
86
+ // otherwise set it on the program.
87
+
88
+
89
+ if (program.body.length > 0) {
90
+ const firstStatement = program.body[0];
91
+ const leadingComments = getLeadingCommentsForNode(firstStatement);
92
+
93
+ if (!leadingComments.includes(docblockComment)) {
94
+ setCommentsOnNode(firstStatement, [docblockComment, ...getCommentsForNode(firstStatement)]);
95
+
96
+ if (isDocblockCommentNew) {
97
+ // Add markers to the code block to ensure docblock comment is printed on its
98
+ // own line.
99
+ code = makeCommentOwnLine(code, docblockComment);
100
+ }
101
+ }
102
+ } else {
103
+ setCommentsOnNode(program, [docblockComment]);
104
+ }
105
+ } // Should not be needed anymore
106
+ // $FlowExpectedError[cannot-write]
107
+
108
+
109
+ delete program.docblock;
110
+ return code;
111
+ }
112
+
61
113
  function moveCommentsToNewNode(oldNode, newNode) {
62
114
  setCommentsOnNode(newNode, getCommentsForNode(oldNode));
63
115
  setCommentsOnNode(oldNode, []);
64
116
  }
65
117
 
118
+ function cloneCommentsToNewNode(oldNode, newNode) {
119
+ setCommentsOnNode(newNode, getCommentsForNode(oldNode).map(comment => cloneCommentWithMarkers(comment)));
120
+ }
121
+
66
122
  function cloneJSDocCommentsToNewNode(oldNode, newNode) {
67
123
  const comments = getCommentsForNode(oldNode).filter(comment => {
68
124
  return (0, _hermesEstree.isBlockComment)(comment) && // JSDoc comments always start with an extra asterisk
@@ -76,28 +132,17 @@ function setCommentsOnNode(node, comments) {
76
132
  node.comments = comments;
77
133
  }
78
134
 
79
- function addCommentsToNode(node, comments, side = 'trailing') {
135
+ function getCommentsForNode(node) {
80
136
  var _node$comments;
81
137
 
82
- // $FlowExpectedError[prop-missing] - this property is secretly added by prettier.
83
- // $FlowExpectedError[cannot-write]
84
- // $FlowExpectedError[incompatible-use]
85
- node.comments = (_node$comments = node.comments) != null ? _node$comments : [];
86
-
87
- if (side === 'leading') {
88
- // $FlowExpectedError[incompatible-cast]
89
- node.comments.unshift(...comments);
90
- } else {
91
- // $FlowExpectedError[incompatible-cast]
92
- node.comments.push(...comments);
93
- }
138
+ // $FlowExpectedError - this property is secretly added by prettier.
139
+ return (_node$comments = node.comments) != null ? _node$comments : [];
94
140
  }
95
141
 
96
- function getCommentsForNode(node) {
97
- var _node$comments2;
98
-
99
- // $FlowExpectedError - this property is secretly added by prettier.
100
- return (_node$comments2 = node.comments) != null ? _node$comments2 : [];
142
+ function isAttachedComment(comment) {
143
+ // Prettier adds this property to comments that have been attached.
144
+ // $FlowExpectedError[prop-missing] - this property is secretly added by prettier.
145
+ return comment.printed === false;
101
146
  }
102
147
 
103
148
  function isLeadingComment(comment) {
@@ -22,6 +22,7 @@ import {
22
22
  addLeadingComment as untypedAddLeadingComment,
23
23
  // $FlowExpectedError[untyped-import]
24
24
  addTrailingComment as untypedAddTrailingComment,
25
+ // $FlowFixMe[untyped-import]
25
26
  } from './prettier/common/util';
26
27
  import {isBlockComment} from 'hermes-estree';
27
28
  import {EOL} from 'os';
@@ -47,6 +48,64 @@ export function attachComments(
47
48
  });
48
49
  }
49
50
 
51
+ export function mutateESTreeASTCommentsForPrettier(
52
+ program: Program,
53
+ text: string,
54
+ ): string {
55
+ let code = text;
56
+
57
+ // we need to delete the comments prop or else prettier will do
58
+ // its own attachment pass after the mutation and duplicate the
59
+ // comments on each node, borking the output
60
+ // $FlowExpectedError[cannot-write]
61
+ delete program.comments;
62
+
63
+ // The docblock comment is never attached to any AST nodes, since its technically
64
+ // attached to the program. However this is specific to our AST and in order for
65
+ // prettier to correctly print it we need to attach it to the first node in the
66
+ // program body.
67
+ if (program.docblock != null && program.docblock.comment != null) {
68
+ const docblockComment = program.docblock.comment;
69
+
70
+ const isDocblockCommentNew = !isAttachedComment(docblockComment);
71
+ if (isDocblockCommentNew) {
72
+ // $FlowExpectedError[prop-missing]
73
+ docblockComment.printed = false;
74
+ // $FlowExpectedError[prop-missing]
75
+ docblockComment.leading = true;
76
+ // $FlowExpectedError[prop-missing]
77
+ docblockComment.trailing = false;
78
+ }
79
+
80
+ // If we have a first node in the program body, attache the comment to that
81
+ // otherwise set it on the program.
82
+ if (program.body.length > 0) {
83
+ const firstStatement = program.body[0];
84
+ const leadingComments = getLeadingCommentsForNode(firstStatement);
85
+ if (!leadingComments.includes(docblockComment)) {
86
+ setCommentsOnNode(firstStatement, [
87
+ docblockComment,
88
+ ...getCommentsForNode(firstStatement),
89
+ ]);
90
+
91
+ if (isDocblockCommentNew) {
92
+ // Add markers to the code block to ensure docblock comment is printed on its
93
+ // own line.
94
+ code = makeCommentOwnLine(code, docblockComment);
95
+ }
96
+ }
97
+ } else {
98
+ setCommentsOnNode(program, [docblockComment]);
99
+ }
100
+ }
101
+
102
+ // Should not be needed anymore
103
+ // $FlowExpectedError[cannot-write]
104
+ delete program.docblock;
105
+
106
+ return code;
107
+ }
108
+
50
109
  export function moveCommentsToNewNode(
51
110
  oldNode: ESNode,
52
111
  newNode: DetachedNode<ESNode>,
@@ -55,6 +114,18 @@ export function moveCommentsToNewNode(
55
114
  setCommentsOnNode(oldNode, []);
56
115
  }
57
116
 
117
+ export function cloneCommentsToNewNode(
118
+ oldNode: MaybeDetachedNode<ESNode>,
119
+ newNode: MaybeDetachedNode<ESNode>,
120
+ ): void {
121
+ setCommentsOnNode(
122
+ newNode,
123
+ getCommentsForNode(oldNode).map(comment =>
124
+ cloneCommentWithMarkers(comment),
125
+ ),
126
+ );
127
+ }
128
+
58
129
  export function cloneJSDocCommentsToNewNode(
59
130
  oldNode: ESNode,
60
131
  newNode: MaybeDetachedNode<ESNode>,
@@ -80,24 +151,6 @@ export function setCommentsOnNode(
80
151
  node.comments = comments;
81
152
  }
82
153
 
83
- export function addCommentsToNode(
84
- node: ESNode | DetachedNode<ESNode>,
85
- comments: $ReadOnlyArray<Comment>,
86
- side?: 'leading' | 'trailing' = 'trailing',
87
- ): void {
88
- // $FlowExpectedError[prop-missing] - this property is secretly added by prettier.
89
- // $FlowExpectedError[cannot-write]
90
- // $FlowExpectedError[incompatible-use]
91
- node.comments = node.comments ?? [];
92
- if (side === 'leading') {
93
- // $FlowExpectedError[incompatible-cast]
94
- (node.comments: Array<Comment>).unshift(...comments);
95
- } else {
96
- // $FlowExpectedError[incompatible-cast]
97
- (node.comments: Array<Comment>).push(...comments);
98
- }
99
- }
100
-
101
154
  export function getCommentsForNode(
102
155
  node: ESNode | DetachedNode<ESNode>,
103
156
  ): $ReadOnlyArray<Comment> {
@@ -105,6 +158,12 @@ export function getCommentsForNode(
105
158
  return node.comments ?? [];
106
159
  }
107
160
 
161
+ export function isAttachedComment(comment: Comment): boolean {
162
+ // Prettier adds this property to comments that have been attached.
163
+ // $FlowExpectedError[prop-missing] - this property is secretly added by prettier.
164
+ return comment.printed === false;
165
+ }
166
+
108
167
  export function isLeadingComment(comment: Comment): boolean {
109
168
  // $FlowExpectedError - this property is secretly added by prettier.
110
169
  return comment.leading === true;
@@ -26,13 +26,6 @@ function createCloneCommentsToMutation(target, destination) {
26
26
  }
27
27
 
28
28
  function performCloneCommentsToMutation(_mutationContext, mutation) {
29
- const newComments = [];
30
-
31
- for (const originalComment of (0, _comments.getCommentsForNode)(mutation.target)) {
32
- const comment = (0, _comments.cloneCommentWithMarkers)(originalComment);
33
- newComments.push(comment);
34
- }
35
-
36
- (0, _comments.addCommentsToNode)(mutation.destination, newComments);
29
+ (0, _comments.cloneCommentsToNewNode)(mutation.target, mutation.destination);
37
30
  return null;
38
31
  }
@@ -12,11 +12,7 @@ import type {ESNode} from 'hermes-estree';
12
12
  import type {DetachedNode} from '../../detachedNode';
13
13
  import type {MutationContext} from '../MutationContext';
14
14
 
15
- import {
16
- addCommentsToNode,
17
- cloneCommentWithMarkers,
18
- getCommentsForNode,
19
- } from '../comments/comments';
15
+ import {cloneCommentsToNewNode} from '../comments/comments';
20
16
 
21
17
  export type CloneCommentsToMutation = $ReadOnly<{
22
18
  type: 'cloneCommentsTo',
@@ -39,12 +35,6 @@ export function performCloneCommentsToMutation(
39
35
  _mutationContext: MutationContext,
40
36
  mutation: CloneCommentsToMutation,
41
37
  ): null {
42
- const newComments = [];
43
- for (const originalComment of getCommentsForNode(mutation.target)) {
44
- const comment = cloneCommentWithMarkers(originalComment);
45
- newComments.push(comment);
46
- }
47
- addCommentsToNode(mutation.destination, newComments);
48
-
38
+ cloneCommentsToNewNode(mutation.target, mutation.destination);
49
39
  return null;
50
40
  }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createModifyNodeInPlaceMutation = createModifyNodeInPlaceMutation;
7
+ exports.performModifyNodeInPlaceMutation = performModifyNodeInPlaceMutation;
8
+
9
+ var _hermesParser = require("hermes-parser");
10
+
11
+ /**
12
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
13
+ *
14
+ * This source code is licensed under the MIT license found in the
15
+ * LICENSE file in the root directory of this source tree.
16
+ *
17
+ *
18
+ * @format
19
+ */
20
+ function createModifyNodeInPlaceMutation(target, newProps) {
21
+ return {
22
+ type: 'modifyNodeInPlace',
23
+ target,
24
+ newProps
25
+ };
26
+ }
27
+
28
+ function performModifyNodeInPlaceMutation(mutationContext, mutation) {
29
+ const {
30
+ target,
31
+ newProps
32
+ } = mutation;
33
+
34
+ for (const [key, newPropValue] of Object.entries(newProps)) {
35
+ const prevPropValue = target[key]; // If the value did not change, skip.
36
+
37
+ if (prevPropValue === newPropValue) {
38
+ continue;
39
+ } // Mark removed nodes as deleted
40
+
41
+
42
+ if ((0, _hermesParser.isNode)(prevPropValue)) {
43
+ mutationContext.markDeletion(prevPropValue);
44
+ } else if (Array.isArray(prevPropValue)) {
45
+ for (const prevPropValueItem of prevPropValue) {
46
+ if ((0, _hermesParser.isNode)(prevPropValueItem)) {
47
+ mutationContext.markDeletion(prevPropValueItem);
48
+ }
49
+ }
50
+ } // Mark node property as mutated.
51
+
52
+
53
+ mutationContext.markMutation(target, key); // Assign new property.
54
+
55
+ target[key] = newPropValue;
56
+ }
57
+
58
+ return target;
59
+ }
@@ -0,0 +1,66 @@
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 {ESNode} from 'hermes-estree';
12
+ import type {MutationContext} from '../MutationContext';
13
+
14
+ import {isNode} from 'hermes-parser';
15
+
16
+ export type ModifyNodeInPlaceMutation = $ReadOnly<{
17
+ type: 'modifyNodeInPlace',
18
+ target: ESNode,
19
+ newProps: $ReadOnly<{...}>,
20
+ }>;
21
+
22
+ export function createModifyNodeInPlaceMutation(
23
+ target: ModifyNodeInPlaceMutation['target'],
24
+ newProps: ModifyNodeInPlaceMutation['newProps'],
25
+ ): ModifyNodeInPlaceMutation {
26
+ return {
27
+ type: 'modifyNodeInPlace',
28
+ target,
29
+ newProps,
30
+ };
31
+ }
32
+
33
+ export function performModifyNodeInPlaceMutation(
34
+ mutationContext: MutationContext,
35
+ mutation: ModifyNodeInPlaceMutation,
36
+ ): ESNode {
37
+ const {target, newProps} = mutation;
38
+
39
+ for (const [key, newPropValue] of Object.entries(newProps)) {
40
+ const prevPropValue = target[key];
41
+
42
+ // If the value did not change, skip.
43
+ if (prevPropValue === newPropValue) {
44
+ continue;
45
+ }
46
+
47
+ // Mark removed nodes as deleted
48
+ if (isNode(prevPropValue)) {
49
+ mutationContext.markDeletion(prevPropValue);
50
+ } else if (Array.isArray(prevPropValue)) {
51
+ for (const prevPropValueItem of prevPropValue) {
52
+ if (isNode(prevPropValueItem)) {
53
+ mutationContext.markDeletion(prevPropValueItem);
54
+ }
55
+ }
56
+ }
57
+
58
+ // Mark node property as mutated.
59
+ mutationContext.markMutation(target, key);
60
+
61
+ // Assign new property.
62
+ target[key] = newPropValue;
63
+ }
64
+
65
+ return target;
66
+ }
@@ -29,28 +29,25 @@ const cacheBase = Math.random();
29
29
 
30
30
  async function print(ast, originalCode, prettierOptions = {}, visitorKeys) {
31
31
  // $FlowExpectedError[incompatible-type] This is now safe to access.
32
- const program = ast; // The docblock comment is never attached to any AST nodes, since its technically
33
- // attached to the program. However this is specific to our AST and in order for
34
- // prettier to correctly print it we need to attach it to the first node in the
35
- // program body.
36
-
37
- if (program.docblock != null && program.body.length > 0) {
38
- const firstNode = program.body[0];
39
- const docblockComment = program.docblock.comment;
40
- const leadingComments = (0, _comments.getLeadingCommentsForNode)(firstNode);
41
-
42
- if (!leadingComments.includes(docblockComment)) {
43
- (0, _comments.addCommentsToNode)(firstNode, [docblockComment], 'leading');
32
+ const program = ast; // If the AST body is empty, we can skip the cost of prettier by returning a static string of the contents.
33
+
34
+ if (program.body.length === 0) {
35
+ var _program$docblock;
36
+
37
+ // If the program had a docblock comment, we need to create the string manually.
38
+ const docblockComment = (_program$docblock = program.docblock) == null ? void 0 : _program$docblock.comment;
39
+
40
+ if (docblockComment != null) {
41
+ return '/*' + docblockComment.value + '*/\n';
44
42
  }
45
- } // Fix up the AST to match what prettier expects.
43
+
44
+ return '';
45
+ } // Cleanup the comments from the AST and generate the "orginal" code needed for prettier.
46
46
 
47
47
 
48
- (0, _hermesParser.mutateESTreeASTForPrettier)(program, visitorKeys); // we need to delete the comments prop or else prettier will do
49
- // its own attachment pass after the mutation and duplicate the
50
- // comments on each node, borking the output
51
- // $FlowExpectedError[cannot-write]
48
+ const codeForPrinting = (0, _comments.mutateESTreeASTCommentsForPrettier)(program, originalCode); // Fix up the AST to match what prettier expects.
52
49
 
53
- delete program.comments;
50
+ (0, _hermesParser.mutateESTreeASTForPrettier)(program, visitorKeys);
54
51
 
55
52
  switch (getPrettierMajorVersion()) {
56
53
  case '3':
@@ -58,7 +55,7 @@ async function print(ast, originalCode, prettierOptions = {}, visitorKeys) {
58
55
  // Lazy require this module as it only exists in prettier v3.
59
56
  const prettierFlowPlugin = require('prettier/plugins/flow');
60
57
 
61
- return prettier.format(originalCode, // $FlowExpectedError[incompatible-exact] - we don't want to create a dependency on the prettier types
58
+ return prettier.format(codeForPrinting, // $FlowExpectedError[incompatible-exact] - we don't want to create a dependency on the prettier types
62
59
  { ...prettierOptions,
63
60
  parser: 'flow',
64
61
  requirePragma: false,
@@ -88,7 +85,7 @@ async function print(ast, originalCode, prettierOptions = {}, visitorKeys) {
88
85
  throw new Error('Hermes parser plugin not found');
89
86
  }
90
87
 
91
- return prettier.format(originalCode, // $FlowExpectedError[incompatible-exact] - we don't want to create a dependency on the prettier types
88
+ return prettier.format(codeForPrinting, // $FlowExpectedError[incompatible-exact] - we don't want to create a dependency on the prettier types
92
89
  { ...prettierOptions,
93
90
  parser: 'hermes',
94
91
  requirePragma: false,
@@ -15,10 +15,7 @@ import type {Program} from 'hermes-estree';
15
15
 
16
16
  import {mutateESTreeASTForPrettier} from 'hermes-parser';
17
17
  import * as prettier from 'prettier';
18
- import {
19
- addCommentsToNode,
20
- getLeadingCommentsForNode,
21
- } from './comments/comments';
18
+ import {mutateESTreeASTCommentsForPrettier} from './comments/comments';
22
19
  import type {VisitorKeysType} from 'hermes-parser';
23
20
 
24
21
  let cache = 1;
@@ -33,34 +30,32 @@ export async function print(
33
30
  // $FlowExpectedError[incompatible-type] This is now safe to access.
34
31
  const program: Program = ast;
35
32
 
36
- // The docblock comment is never attached to any AST nodes, since its technically
37
- // attached to the program. However this is specific to our AST and in order for
38
- // prettier to correctly print it we need to attach it to the first node in the
39
- // program body.
40
- if (program.docblock != null && program.body.length > 0) {
41
- const firstNode = program.body[0];
42
- const docblockComment = program.docblock.comment;
43
- const leadingComments = getLeadingCommentsForNode(firstNode);
44
- if (!leadingComments.includes(docblockComment)) {
45
- addCommentsToNode(firstNode, [docblockComment], 'leading');
33
+ // If the AST body is empty, we can skip the cost of prettier by returning a static string of the contents.
34
+ if (program.body.length === 0) {
35
+ // If the program had a docblock comment, we need to create the string manually.
36
+ const docblockComment = program.docblock?.comment;
37
+ if (docblockComment != null) {
38
+ return '/*' + docblockComment.value + '*/\n';
46
39
  }
40
+
41
+ return '';
47
42
  }
48
43
 
44
+ // Cleanup the comments from the AST and generate the "orginal" code needed for prettier.
45
+ const codeForPrinting = mutateESTreeASTCommentsForPrettier(
46
+ program,
47
+ originalCode,
48
+ );
49
+
49
50
  // Fix up the AST to match what prettier expects.
50
51
  mutateESTreeASTForPrettier(program, visitorKeys);
51
52
 
52
- // we need to delete the comments prop or else prettier will do
53
- // its own attachment pass after the mutation and duplicate the
54
- // comments on each node, borking the output
55
- // $FlowExpectedError[cannot-write]
56
- delete program.comments;
57
-
58
53
  switch (getPrettierMajorVersion()) {
59
54
  case '3': {
60
55
  // Lazy require this module as it only exists in prettier v3.
61
56
  const prettierFlowPlugin = require('prettier/plugins/flow');
62
57
  return prettier.format(
63
- originalCode,
58
+ codeForPrinting,
64
59
  // $FlowExpectedError[incompatible-exact] - we don't want to create a dependency on the prettier types
65
60
  {
66
61
  ...prettierOptions,
@@ -89,7 +84,7 @@ export async function print(
89
84
  }
90
85
 
91
86
  return prettier.format(
92
- originalCode,
87
+ codeForPrinting,
93
88
  // $FlowExpectedError[incompatible-exact] - we don't want to create a dependency on the prettier types
94
89
  {
95
90
  ...prettierOptions,
@@ -38,6 +38,8 @@ var _ReplaceNode = require("./mutations/ReplaceNode");
38
38
 
39
39
  var _ReplaceStatementWithMany = require("./mutations/ReplaceStatementWithMany");
40
40
 
41
+ var _ModifyNodeInPlace = require("./mutations/ModifyNodeInPlace");
42
+
41
43
  function transformAST({
42
44
  ast,
43
45
  scopeManager,
@@ -53,6 +55,11 @@ function transformAST({
53
55
  for (const mutation of transformContext.mutations) {
54
56
  const mutationRoot = (() => {
55
57
  switch (mutation.type) {
58
+ case 'modifyNodeInPlace':
59
+ {
60
+ return (0, _ModifyNodeInPlace.performModifyNodeInPlaceMutation)(mutationContext, mutation);
61
+ }
62
+
56
63
  case 'insertStatement':
57
64
  {
58
65
  return (0, _InsertStatement.performInsertStatementMutation)(mutationContext, mutation);
@@ -27,6 +27,7 @@ import {performRemoveNodeMutation} from './mutations/RemoveNode';
27
27
  import {performRemoveStatementMutation} from './mutations/RemoveStatement';
28
28
  import {performReplaceNodeMutation} from './mutations/ReplaceNode';
29
29
  import {performReplaceStatementWithManyMutation} from './mutations/ReplaceStatementWithMany';
30
+ import {performModifyNodeInPlaceMutation} from './mutations/ModifyNodeInPlace';
30
31
 
31
32
  export type TransformASTResult = {
32
33
  ast: Program,
@@ -56,6 +57,10 @@ export function transformAST(
56
57
  for (const mutation of transformContext.mutations) {
57
58
  const mutationRoot = ((): ESNode | null => {
58
59
  switch (mutation.type) {
60
+ case 'modifyNodeInPlace': {
61
+ return performModifyNodeInPlaceMutation(mutationContext, mutation);
62
+ }
63
+
59
64
  case 'insertStatement': {
60
65
  return performInsertStatementMutation(mutationContext, mutation);
61
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermes-transform",
3
- "version": "0.22.0",
3
+ "version": "0.23.1",
4
4
  "description": "Tools built on top of Hermes-ESTree to enable codebase transformation",
5
5
  "main": "dist/index.js",
6
6
  "license": "MIT",
@@ -12,14 +12,14 @@
12
12
  "@babel/code-frame": "^7.16.0",
13
13
  "esquery": "^1.4.0",
14
14
  "flow-enums-runtime": "^0.0.6",
15
- "hermes-eslint": "0.22.0",
16
- "hermes-estree": "0.22.0",
17
- "hermes-parser": "0.22.0",
15
+ "hermes-eslint": "0.23.1",
16
+ "hermes-estree": "0.23.1",
17
+ "hermes-parser": "0.23.1",
18
18
  "string-width": "4.2.3"
19
19
  },
20
20
  "peerDependencies": {
21
21
  "prettier": "^3.0.0 || ^2.7.1",
22
- "prettier-plugin-hermes-parser": "0.22.0"
22
+ "prettier-plugin-hermes-parser": "0.23.1"
23
23
  },
24
24
  "files": [
25
25
  "dist",