hermes-transform 0.8.0 → 0.10.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 (65) hide show
  1. package/ESLINT_LICENCE +19 -0
  2. package/PRETTIER_LICENCE +7 -0
  3. package/dist/detachedNode.js +100 -42
  4. package/dist/detachedNode.js.flow +116 -41
  5. package/dist/generated/TransformCloneSignatures.js.flow +18 -0
  6. package/dist/generated/TransformModifySignatures.js.flow +1127 -0
  7. package/dist/generated/TransformReplaceSignatures.js.flow +15 -1
  8. package/dist/generated/node-types.js +852 -883
  9. package/dist/generated/node-types.js.flow +1187 -1217
  10. package/dist/generated/special-case-node-types/Comment.js +36 -0
  11. package/dist/generated/special-case-node-types/Comment.js.flow +36 -0
  12. package/dist/generated/special-case-node-types/DeclareExportDeclaration.js +55 -0
  13. package/dist/generated/special-case-node-types/DeclareExportDeclaration.js.flow +97 -0
  14. package/dist/generated/special-case-node-types/ExportNamedDeclaration.js +42 -0
  15. package/dist/generated/special-case-node-types/ExportNamedDeclaration.js.flow +75 -0
  16. package/dist/generated/special-case-node-types/Literal.js +97 -0
  17. package/dist/generated/special-case-node-types/Literal.js.flow +139 -0
  18. package/dist/generated/special-case-node-types/ObjectTypeProperty.js +73 -0
  19. package/dist/generated/special-case-node-types/ObjectTypeProperty.js.flow +107 -0
  20. package/dist/generated/special-case-node-types/Property.js +136 -0
  21. package/dist/generated/special-case-node-types/Property.js.flow +237 -0
  22. package/dist/generated/special-case-node-types/misc.js +119 -0
  23. package/dist/generated/special-case-node-types/misc.js.flow +205 -0
  24. package/dist/generated/special-case-node-types.js +42 -180
  25. package/dist/generated/special-case-node-types.js.flow +7 -258
  26. package/dist/index.js +19 -3
  27. package/dist/index.js.flow +6 -2
  28. package/dist/transform/TransformContext.js +34 -11
  29. package/dist/transform/TransformContext.js.flow +90 -33
  30. package/dist/transform/comments/comments.js +34 -5
  31. package/dist/transform/comments/comments.js.flow +39 -4
  32. package/dist/transform/comments/prettier/main/comments.js +1 -1
  33. package/dist/transform/comments/prettier/main/comments.js.flow +2 -1
  34. package/dist/transform/mutations/InsertStatement.js +4 -3
  35. package/dist/transform/mutations/InsertStatement.js.flow +4 -3
  36. package/dist/transform/mutations/RemoveComment.js +3 -3
  37. package/dist/transform/mutations/RemoveComment.js.flow +3 -5
  38. package/dist/transform/mutations/RemoveNode.js +2 -2
  39. package/dist/transform/mutations/RemoveNode.js.flow +2 -2
  40. package/dist/transform/mutations/RemoveStatement.js +2 -2
  41. package/dist/transform/mutations/RemoveStatement.js.flow +2 -2
  42. package/dist/transform/mutations/ReplaceNode.js +10 -7
  43. package/dist/transform/mutations/ReplaceNode.js.flow +7 -5
  44. package/dist/transform/mutations/ReplaceStatementWithMany.js +2 -2
  45. package/dist/transform/mutations/ReplaceStatementWithMany.js.flow +7 -4
  46. package/dist/transform/mutations/utils/getStatementParent.js +3 -2
  47. package/dist/transform/mutations/utils/getStatementParent.js.flow +5 -2
  48. package/dist/transform/parse.js +55 -0
  49. package/dist/transform/parse.js.flow +55 -0
  50. package/dist/transform/print.js +160 -0
  51. package/dist/transform/print.js.flow +176 -0
  52. package/dist/transform/transform.js +6 -67
  53. package/dist/transform/transform.js.flow +6 -69
  54. package/dist/transform/{getTransformedAST.js → transformAST.js} +7 -16
  55. package/dist/transform/{getTransformedAST.js.flow → transformAST.js.flow} +7 -14
  56. package/dist/traverse/NodeEventGenerator.js.flow +1 -1
  57. package/dist/traverse/traverse.js +36 -35
  58. package/dist/traverse/traverse.js.flow +46 -27
  59. package/package.json +10 -5
  60. package/dist/getVisitorKeys.js +0 -33
  61. package/dist/getVisitorKeys.js.flow +0 -31
  62. package/dist/transform/mutations/utils/arrayUtils.js +0 -43
  63. package/dist/transform/mutations/utils/arrayUtils.js.flow +0 -50
  64. package/dist/traverse/SimpleTraverser.js +0 -118
  65. package/dist/traverse/SimpleTraverser.js.flow +0 -112
@@ -12,7 +12,7 @@ import type {ESNode, ModuleDeclaration, Statement} from 'hermes-estree';
12
12
  import type {MutationContext} from '../MutationContext';
13
13
  import type {DetachedNode} from '../../detachedNode';
14
14
 
15
- import {removeFromArray} from './utils/arrayUtils';
15
+ import {astArrayMutationHelpers} from 'hermes-parser';
16
16
  import {getStatementParent} from './utils/getStatementParent';
17
17
  import * as t from '../../generated/node-types';
18
18
 
@@ -43,7 +43,7 @@ export function performRemoveStatementMutation(
43
43
  const parent: interface {
44
44
  [string]: $ReadOnlyArray<DetachedNode<Statement | ModuleDeclaration>>,
45
45
  } = removalParent.parent;
46
- parent[removalParent.key] = removeFromArray(
46
+ parent[removalParent.key] = astArrayMutationHelpers.removeFromArray(
47
47
  parent[removalParent.key],
48
48
  removalParent.targetIndex,
49
49
  );
@@ -6,13 +6,13 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.createReplaceNodeMutation = createReplaceNodeMutation;
7
7
  exports.performReplaceNodeMutation = performReplaceNodeMutation;
8
8
 
9
- var _arrayUtils = require("./utils/arrayUtils");
9
+ var _hermesParser = require("hermes-parser");
10
10
 
11
11
  var _comments = require("../comments/comments");
12
12
 
13
13
  var _Errors = require("../Errors");
14
14
 
15
- var _getVisitorKeys = require("../../getVisitorKeys");
15
+ var _detachedNode = require("../../detachedNode");
16
16
 
17
17
  /**
18
18
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -44,7 +44,7 @@ function performReplaceNodeMutation(mutationContext, mutation) {
44
44
 
45
45
  if (replacementParent.type === 'array') {
46
46
  const parent = replacementParent.parent;
47
- parent[replacementParent.key] = (0, _arrayUtils.replaceInArray)(parent[replacementParent.key], replacementParent.targetIndex, [mutation.nodeToReplaceWith]);
47
+ parent[replacementParent.key] = _hermesParser.astArrayMutationHelpers.replaceInArray(parent[replacementParent.key], replacementParent.targetIndex, [mutation.nodeToReplaceWith]);
48
48
  } else {
49
49
  replacementParent.parent[replacementParent.key] = mutation.nodeToReplaceWith;
50
50
  }
@@ -59,8 +59,8 @@ function performReplaceNodeMutation(mutationContext, mutation) {
59
59
  function getParentKey(target) {
60
60
  const parent = target.parent;
61
61
 
62
- for (const key of (0, _getVisitorKeys.getVisitorKeys)(parent)) {
63
- if ((0, _getVisitorKeys.isNode)( // $FlowExpectedError[prop-missing]
62
+ for (const key of (0, _hermesParser.getVisitorKeys)(parent)) {
63
+ if ((0, _hermesParser.isNode)( // $FlowExpectedError[prop-missing]
64
64
  parent[key])) {
65
65
  if (parent[key] === target) {
66
66
  return {
@@ -71,7 +71,10 @@ function getParentKey(target) {
71
71
  }
72
72
  } else if (Array.isArray(parent[key])) {
73
73
  for (let i = 0; i < parent[key].length; i += 1) {
74
- if (parent[key][i] === target) {
74
+ const current = parent[key][i];
75
+ const originalNode = (0, _detachedNode.getOriginalNode)(current);
76
+
77
+ if (current === target || originalNode === target) {
75
78
  return {
76
79
  type: 'array',
77
80
  parent,
@@ -84,5 +87,5 @@ function getParentKey(target) {
84
87
  } // this shouldn't happen ever
85
88
 
86
89
 
87
- throw new _Errors.InvalidReplacementError(`Expected to find the ${target.type} as a direct child of the ${target.type}.`);
90
+ throw new _Errors.InvalidReplacementError(`Expected to find the ${target.type} as a direct child of the ${parent.type}.`);
88
91
  }
@@ -12,10 +12,10 @@ import type {ESNode} from 'hermes-estree';
12
12
  import type {MutationContext} from '../MutationContext';
13
13
  import type {DetachedNode} from '../../detachedNode';
14
14
 
15
- import {replaceInArray} from './utils/arrayUtils';
15
+ import {getVisitorKeys, isNode, astArrayMutationHelpers} from 'hermes-parser';
16
16
  import {moveCommentsToNewNode} from '../comments/comments';
17
17
  import {InvalidReplacementError} from '../Errors';
18
- import {getVisitorKeys, isNode} from '../../getVisitorKeys';
18
+ import {getOriginalNode} from '../../detachedNode';
19
19
 
20
20
  export type ReplaceNodeMutation = $ReadOnly<{
21
21
  type: 'replaceNode',
@@ -55,7 +55,7 @@ export function performReplaceNodeMutation(
55
55
  const parent: interface {
56
56
  [string]: $ReadOnlyArray<DetachedNode<ESNode>>,
57
57
  } = replacementParent.parent;
58
- parent[replacementParent.key] = replaceInArray(
58
+ parent[replacementParent.key] = astArrayMutationHelpers.replaceInArray(
59
59
  parent[replacementParent.key],
60
60
  replacementParent.targetIndex,
61
61
  [mutation.nodeToReplaceWith],
@@ -99,7 +99,9 @@ function getParentKey(target: ESNode): $ReadOnly<
99
99
  }
100
100
  } else if (Array.isArray(parent[key])) {
101
101
  for (let i = 0; i < parent[key].length; i += 1) {
102
- if (parent[key][i] === target) {
102
+ const current = parent[key][i];
103
+ const originalNode = getOriginalNode(current);
104
+ if (current === target || originalNode === target) {
103
105
  return {type: 'array', parent, key, targetIndex: i};
104
106
  }
105
107
  }
@@ -108,6 +110,6 @@ function getParentKey(target: ESNode): $ReadOnly<
108
110
 
109
111
  // this shouldn't happen ever
110
112
  throw new InvalidReplacementError(
111
- `Expected to find the ${target.type} as a direct child of the ${target.type}.`,
113
+ `Expected to find the ${target.type} as a direct child of the ${parent.type}.`,
112
114
  );
113
115
  }
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.createReplaceStatementWithManyMutation = createReplaceStatementWithManyMutation;
7
7
  exports.performReplaceStatementWithManyMutation = performReplaceStatementWithManyMutation;
8
8
 
9
- var _arrayUtils = require("./utils/arrayUtils");
9
+ var _hermesParser = require("hermes-parser");
10
10
 
11
11
  var _getStatementParent = require("./utils/getStatementParent");
12
12
 
@@ -63,7 +63,7 @@ function performReplaceStatementWithManyMutation(mutationContext, mutation) {
63
63
 
64
64
  if (replacementParent.type === 'array') {
65
65
  const parent = replacementParent.parent;
66
- parent[replacementParent.key] = (0, _arrayUtils.replaceInArray)(parent[replacementParent.key], replacementParent.targetIndex, mutation.nodesToReplaceWith);
66
+ parent[replacementParent.key] = _hermesParser.astArrayMutationHelpers.replaceInArray(parent[replacementParent.key], replacementParent.targetIndex, mutation.nodesToReplaceWith);
67
67
  return replacementParent.parent;
68
68
  }
69
69
 
@@ -12,18 +12,21 @@ import type {ESNode, ModuleDeclaration, Statement} from 'hermes-estree';
12
12
  import type {MutationContext} from '../MutationContext';
13
13
  import type {DetachedNode} from '../../detachedNode';
14
14
 
15
- import {replaceInArray} from './utils/arrayUtils';
15
+ import {astArrayMutationHelpers} from 'hermes-parser';
16
16
  import {getStatementParent} from './utils/getStatementParent';
17
17
  import {isValidModuleDeclarationParent} from './utils/isValidModuleDeclarationParent';
18
18
  import {moveCommentsToNewNode} from '../comments/comments';
19
19
  import {InvalidReplacementError} from '../Errors';
20
20
  import * as t from '../../generated/node-types';
21
21
 
22
+ export type ReplaceStatementWithManyMutationNodes =
23
+ | ModuleDeclaration
24
+ | Statement;
22
25
  export type ReplaceStatementWithManyMutation = $ReadOnly<{
23
26
  type: 'replaceStatementWithMany',
24
- target: ModuleDeclaration | Statement,
27
+ target: ReplaceStatementWithManyMutationNodes,
25
28
  nodesToReplaceWith: $ReadOnlyArray<
26
- DetachedNode<ModuleDeclaration | Statement>,
29
+ DetachedNode<ReplaceStatementWithManyMutationNodes>,
27
30
  >,
28
31
  keepComments: boolean,
29
32
  }>;
@@ -75,7 +78,7 @@ export function performReplaceStatementWithManyMutation(
75
78
  const parent: interface {
76
79
  [string]: $ReadOnlyArray<DetachedNode<Statement | ModuleDeclaration>>,
77
80
  } = replacementParent.parent;
78
- parent[replacementParent.key] = replaceInArray(
81
+ parent[replacementParent.key] = astArrayMutationHelpers.replaceInArray(
79
82
  parent[replacementParent.key],
80
83
  replacementParent.targetIndex,
81
84
  mutation.nodesToReplaceWith,
@@ -97,8 +97,9 @@ function getStatementParent(target) {
97
97
  case 'ForInStatement':
98
98
  case 'ForOfStatement':
99
99
  {
100
- assertValidStatementLocation( // $FlowExpectedError[prop-missing] - flow does not track properties from parent interface
101
- parent, 'left', 'right');
100
+ assertValidStatementLocation(parent, // $FlowExpectedError[prop-missing] - flow does not track properties from parent interface
101
+ 'left', // $FlowExpectedError[prop-missing] - flow does not track properties from parent interface
102
+ 'right');
102
103
  return {
103
104
  type: 'single',
104
105
  parent,
@@ -9,6 +9,8 @@
9
9
  */
10
10
 
11
11
  import type {
12
+ ForInStatement,
13
+ ForOfStatement,
12
14
  ModuleDeclaration,
13
15
  Statement,
14
16
  StatementParentArray,
@@ -92,10 +94,11 @@ export function getStatementParent(
92
94
 
93
95
  case 'ForInStatement':
94
96
  case 'ForOfStatement': {
95
- assertValidStatementLocation(
96
- // $FlowExpectedError[prop-missing] - flow does not track properties from parent interface
97
+ assertValidStatementLocation<ForInStatement | ForOfStatement>(
97
98
  parent,
99
+ // $FlowExpectedError[prop-missing] - flow does not track properties from parent interface
98
100
  'left',
101
+ // $FlowExpectedError[prop-missing] - flow does not track properties from parent interface
99
102
  'right',
100
103
  );
101
104
  return {type: 'single', parent, key: 'body'};
@@ -0,0 +1,55 @@
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
+ *
8
+ * @format
9
+ */
10
+ 'use strict';
11
+
12
+ Object.defineProperty(exports, "__esModule", {
13
+ value: true
14
+ });
15
+ exports.parse = parse;
16
+
17
+ var _comments = require("./comments/comments");
18
+
19
+ var _hermesEslint = require("hermes-eslint");
20
+
21
+ function parse(code) {
22
+ const {
23
+ ast,
24
+ scopeManager
25
+ } = (0, _hermesEslint.parseForESLint)(code, {
26
+ sourceType: 'module'
27
+ }); // Don't include the docblock comment in the comment list as we don't want to attach it
28
+ // as it should be maintained at the top of the file as nodes are moved around.
29
+
30
+ let comments = ast.comments;
31
+
32
+ if (ast.docblock != null && ast.docblock.comment === comments[0]) {
33
+ const [first, ...nonDocblockComments] = comments; // Since we will not be attaching this comment automatically we need to add the
34
+ // properties prettier expects for printing.
35
+ // $FlowExpectedError[prop-missing]
36
+
37
+ first.placement = 'endOfLine'; // $FlowExpectedError[prop-missing]
38
+
39
+ first.leading = true; // $FlowExpectedError[prop-missing]
40
+
41
+ first.trailing = false; // $FlowExpectedError[prop-missing]
42
+
43
+ first.printed = false;
44
+ comments = nonDocblockComments;
45
+ } // attach comments before mutation. this will ensure that as nodes are
46
+ // cloned / moved around - comments remain in the correct place with respect to the node
47
+
48
+
49
+ (0, _comments.attachComments)(comments, ast, code);
50
+ return {
51
+ ast,
52
+ scopeManager,
53
+ code
54
+ };
55
+ }
@@ -0,0 +1,55 @@
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
+ 'use strict';
12
+
13
+ import type {Program} from 'hermes-estree';
14
+ import type {ScopeManager} from 'hermes-eslint';
15
+
16
+ import {attachComments} from './comments/comments';
17
+ import {parseForESLint} from 'hermes-eslint';
18
+
19
+ export type ParseResult = {
20
+ ast: Program,
21
+ scopeManager: ScopeManager,
22
+ code: string,
23
+ };
24
+
25
+ export function parse(code: string): ParseResult {
26
+ const {ast, scopeManager} = parseForESLint(code, {
27
+ sourceType: 'module',
28
+ });
29
+
30
+ // Don't include the docblock comment in the comment list as we don't want to attach it
31
+ // as it should be maintained at the top of the file as nodes are moved around.
32
+ let comments = ast.comments;
33
+ if (ast.docblock != null && ast.docblock.comment === comments[0]) {
34
+ const [first, ...nonDocblockComments] = comments;
35
+
36
+ // Since we will not be attaching this comment automatically we need to add the
37
+ // properties prettier expects for printing.
38
+ // $FlowExpectedError[prop-missing]
39
+ first.placement = 'endOfLine';
40
+ // $FlowExpectedError[prop-missing]
41
+ first.leading = true;
42
+ // $FlowExpectedError[prop-missing]
43
+ first.trailing = false;
44
+ // $FlowExpectedError[prop-missing]
45
+ first.printed = false;
46
+
47
+ comments = nonDocblockComments;
48
+ }
49
+
50
+ // attach comments before mutation. this will ensure that as nodes are
51
+ // cloned / moved around - comments remain in the correct place with respect to the node
52
+ attachComments(comments, ast, code);
53
+
54
+ return {ast, scopeManager, code};
55
+ }
@@ -0,0 +1,160 @@
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
+ *
8
+ * @format
9
+ */
10
+ 'use strict';
11
+
12
+ Object.defineProperty(exports, "__esModule", {
13
+ value: true
14
+ });
15
+ exports.print = print;
16
+
17
+ var _hermesParser = require("hermes-parser");
18
+
19
+ var prettier = _interopRequireWildcard(require("prettier"));
20
+
21
+ var _comments = require("./comments/comments");
22
+
23
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
24
+
25
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
26
+
27
+ function print(ast, originalCode, prettierOptions = {}, visitorKeys) {
28
+ // $FlowExpectedError[incompatible-type] This is now safe to access.
29
+ const program = ast; // The docblock comment is never attached to any AST nodes, since its technically
30
+ // attached to the program. However this is specific to our AST and in order for
31
+ // prettier to correctly print it we need to attach it to the first node in the
32
+ // program body.
33
+
34
+ if (program.docblock != null && program.body.length > 0) {
35
+ const firstNode = program.body[0];
36
+ const docblockComment = program.docblock.comment;
37
+ const leadingComments = (0, _comments.getLeadingCommentsForNode)(firstNode);
38
+
39
+ if (!leadingComments.includes(docblockComment)) {
40
+ (0, _comments.addCommentsToNode)(firstNode, [docblockComment], 'leading');
41
+ }
42
+ } // Fix up the AST to match what prettier expects.
43
+
44
+
45
+ mutateASTForPrettier(program, visitorKeys); // we need to delete the comments prop or else prettier will do
46
+ // its own attachment pass after the mutation and duplicate the
47
+ // comments on each node, borking the output
48
+ // $FlowExpectedError[cannot-write]
49
+
50
+ delete program.comments;
51
+ return prettier.format(originalCode, // $FlowExpectedError[incompatible-exact] - we don't want to create a dependency on the prettier types
52
+ { ...prettierOptions,
53
+
54
+ parser() {
55
+ return program;
56
+ }
57
+
58
+ });
59
+ }
60
+
61
+ function mutateASTForPrettier(rootNode, visitorKeys) {
62
+ _hermesParser.SimpleTraverser.traverse(rootNode, {
63
+ enter(node) {
64
+ // prettier fully expects the parent pointers are NOT set and
65
+ // certain cases can crash due to prettier infinite-looping
66
+ // whilst naively traversing the parent property
67
+ // https://github.com/prettier/prettier/issues/11793
68
+ // $FlowExpectedError[cannot-write]
69
+ delete node.parent; // prettier currently relies on the AST being in the old-school, deprecated AST format for optional chaining
70
+ // so we have to apply their transform to our AST so it can actually format it.
71
+
72
+ if (node.type === 'ChainExpression') {
73
+ const newNode = transformChainExpression(node.expression); // Clear out existing properties
74
+
75
+ for (const k of Object.keys(node)) {
76
+ // $FlowExpectedError[prop-missing]
77
+ delete node[k];
78
+ } // Traverse `newNode` and its children.
79
+
80
+
81
+ mutateASTForPrettier(newNode, visitorKeys); // Overwrite `node` to match `newNode` while retaining the reference.
82
+ // $FlowExpectedError[prop-missing]
83
+ // $FlowExpectedError[cannot-write]
84
+
85
+ Object.assign(node, newNode); // Skip traversing the existing nodes since we are replacing them.
86
+
87
+ throw _hermesParser.SimpleTraverser.Skip;
88
+ } // Prettier currently relies on comparing the `node` vs `node.value` start positions to know if an
89
+ // `ObjectTypeProperty` is a method or not (instead of using the `node.method` boolean). To correctly print
90
+ // the node when its not a method we need the start position to be different from the `node.value`s start
91
+ // position.
92
+
93
+
94
+ if (node.type === 'ObjectTypeProperty') {
95
+ if (node.method === false && node.kind === 'init' && node.range[0] === 1 && node.value.range[0] === 1) {
96
+ // $FlowExpectedError[cannot-write]
97
+ // $FlowExpectedError[cannot-spread-interface]
98
+ node.value = { ...node.value,
99
+ range: [2, node.value.range[1]]
100
+ };
101
+ }
102
+ } // Prettier currently relies on comparing the the start positions to know if the import/export specifier should have a
103
+ // rename (eg `Name` vs `Name as Name`) when the name is exactly the same
104
+ // So we need to ensure that the range is always the same to avoid the useless code printing
105
+
106
+
107
+ if (node.type === 'ImportSpecifier') {
108
+ if (node.local.name === node.imported.name) {
109
+ if (node.local.range == null) {
110
+ // for our TS-ast printing which has no locs
111
+ // $FlowExpectedError[cannot-write]
112
+ node.local.range = [0, 0];
113
+ } // $FlowExpectedError[cannot-write]
114
+
115
+
116
+ node.imported.range = [...node.local.range];
117
+ }
118
+ }
119
+
120
+ if (node.type === 'ExportSpecifier') {
121
+ if (node.local.name === node.exported.name) {
122
+ if (node.local.range == null) {
123
+ // for our TS-ast printing which has no locs
124
+ // $FlowExpectedError[cannot-write]
125
+ node.local.range = [0, 0];
126
+ } // $FlowExpectedError[cannot-write]
127
+
128
+
129
+ node.exported.range = [...node.local.range];
130
+ }
131
+ }
132
+ },
133
+
134
+ leave() {},
135
+
136
+ visitorKeys
137
+ });
138
+ } // https://github.com/prettier/prettier/blob/d962466a828f8ef51435e3e8840178d90b7ec6cd/src/language-js/parse/postprocess/index.js#L161-L182
139
+
140
+
141
+ function transformChainExpression(node) {
142
+ switch (node.type) {
143
+ case 'CallExpression':
144
+ // $FlowExpectedError[cannot-spread-interface]
145
+ return { ...node,
146
+ type: 'OptionalCallExpression',
147
+ callee: transformChainExpression(node.callee)
148
+ };
149
+
150
+ case 'MemberExpression':
151
+ // $FlowExpectedError[cannot-spread-interface]
152
+ return { ...node,
153
+ type: 'OptionalMemberExpression',
154
+ object: transformChainExpression(node.object)
155
+ };
156
+ // No default
157
+ }
158
+
159
+ return node;
160
+ }
@@ -0,0 +1,176 @@
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
+ 'use strict';
12
+
13
+ import type {MaybeDetachedNode} from '../detachedNode';
14
+ import type {Program, ESNode} from 'hermes-estree';
15
+
16
+ import {SimpleTraverser} from 'hermes-parser';
17
+ import * as prettier from 'prettier';
18
+ import {
19
+ addCommentsToNode,
20
+ getLeadingCommentsForNode,
21
+ } from './comments/comments';
22
+ import type {VisitorKeysType} from 'hermes-parser';
23
+
24
+ export function print(
25
+ ast: MaybeDetachedNode<Program>,
26
+ originalCode: string,
27
+ prettierOptions: {...} = {},
28
+ visitorKeys?: ?VisitorKeysType,
29
+ ): string {
30
+ // $FlowExpectedError[incompatible-type] This is now safe to access.
31
+ const program: Program = ast;
32
+
33
+ // The docblock comment is never attached to any AST nodes, since its technically
34
+ // attached to the program. However this is specific to our AST and in order for
35
+ // prettier to correctly print it we need to attach it to the first node in the
36
+ // program body.
37
+ if (program.docblock != null && program.body.length > 0) {
38
+ const firstNode = program.body[0];
39
+ const docblockComment = program.docblock.comment;
40
+ const leadingComments = getLeadingCommentsForNode(firstNode);
41
+ if (!leadingComments.includes(docblockComment)) {
42
+ addCommentsToNode(firstNode, [docblockComment], 'leading');
43
+ }
44
+ }
45
+
46
+ // Fix up the AST to match what prettier expects.
47
+ mutateASTForPrettier(program, visitorKeys);
48
+
49
+ // we need to delete the comments prop or else prettier will do
50
+ // its own attachment pass after the mutation and duplicate the
51
+ // comments on each node, borking the output
52
+ // $FlowExpectedError[cannot-write]
53
+ delete program.comments;
54
+
55
+ return prettier.format(
56
+ originalCode,
57
+ // $FlowExpectedError[incompatible-exact] - we don't want to create a dependency on the prettier types
58
+ {
59
+ ...prettierOptions,
60
+ parser() {
61
+ return program;
62
+ },
63
+ },
64
+ );
65
+ }
66
+
67
+ function mutateASTForPrettier(
68
+ rootNode: ESNode,
69
+ visitorKeys: ?VisitorKeysType,
70
+ ): void {
71
+ SimpleTraverser.traverse(rootNode, {
72
+ enter(node) {
73
+ // prettier fully expects the parent pointers are NOT set and
74
+ // certain cases can crash due to prettier infinite-looping
75
+ // whilst naively traversing the parent property
76
+ // https://github.com/prettier/prettier/issues/11793
77
+ // $FlowExpectedError[cannot-write]
78
+ delete node.parent;
79
+
80
+ // prettier currently relies on the AST being in the old-school, deprecated AST format for optional chaining
81
+ // so we have to apply their transform to our AST so it can actually format it.
82
+ if (node.type === 'ChainExpression') {
83
+ const newNode = transformChainExpression(node.expression);
84
+
85
+ // Clear out existing properties
86
+ for (const k of Object.keys(node)) {
87
+ // $FlowExpectedError[prop-missing]
88
+ delete node[k];
89
+ }
90
+
91
+ // Traverse `newNode` and its children.
92
+ mutateASTForPrettier(newNode, visitorKeys);
93
+
94
+ // Overwrite `node` to match `newNode` while retaining the reference.
95
+ // $FlowExpectedError[prop-missing]
96
+ // $FlowExpectedError[cannot-write]
97
+ Object.assign(node, newNode);
98
+
99
+ // Skip traversing the existing nodes since we are replacing them.
100
+ throw SimpleTraverser.Skip;
101
+ }
102
+
103
+ // Prettier currently relies on comparing the `node` vs `node.value` start positions to know if an
104
+ // `ObjectTypeProperty` is a method or not (instead of using the `node.method` boolean). To correctly print
105
+ // the node when its not a method we need the start position to be different from the `node.value`s start
106
+ // position.
107
+ if (node.type === 'ObjectTypeProperty') {
108
+ if (
109
+ node.method === false &&
110
+ node.kind === 'init' &&
111
+ node.range[0] === 1 &&
112
+ node.value.range[0] === 1
113
+ ) {
114
+ // $FlowExpectedError[cannot-write]
115
+ // $FlowExpectedError[cannot-spread-interface]
116
+ node.value = {
117
+ ...node.value,
118
+ range: [2, node.value.range[1]],
119
+ };
120
+ }
121
+ }
122
+
123
+ // Prettier currently relies on comparing the the start positions to know if the import/export specifier should have a
124
+ // rename (eg `Name` vs `Name as Name`) when the name is exactly the same
125
+ // So we need to ensure that the range is always the same to avoid the useless code printing
126
+ if (node.type === 'ImportSpecifier') {
127
+ if (node.local.name === node.imported.name) {
128
+ if (node.local.range == null) {
129
+ // for our TS-ast printing which has no locs
130
+ // $FlowExpectedError[cannot-write]
131
+ node.local.range = [0, 0];
132
+ }
133
+ // $FlowExpectedError[cannot-write]
134
+ node.imported.range = [...node.local.range];
135
+ }
136
+ }
137
+ if (node.type === 'ExportSpecifier') {
138
+ if (node.local.name === node.exported.name) {
139
+ if (node.local.range == null) {
140
+ // for our TS-ast printing which has no locs
141
+ // $FlowExpectedError[cannot-write]
142
+ node.local.range = [0, 0];
143
+ }
144
+ // $FlowExpectedError[cannot-write]
145
+ node.exported.range = [...node.local.range];
146
+ }
147
+ }
148
+ },
149
+ leave() {},
150
+ visitorKeys,
151
+ });
152
+ }
153
+
154
+ // https://github.com/prettier/prettier/blob/d962466a828f8ef51435e3e8840178d90b7ec6cd/src/language-js/parse/postprocess/index.js#L161-L182
155
+ function transformChainExpression(node: ESNode): ESNode {
156
+ switch (node.type) {
157
+ case 'CallExpression':
158
+ // $FlowExpectedError[cannot-spread-interface]
159
+ return {
160
+ ...node,
161
+ type: 'OptionalCallExpression',
162
+ callee: transformChainExpression(node.callee),
163
+ };
164
+
165
+ case 'MemberExpression':
166
+ // $FlowExpectedError[cannot-spread-interface]
167
+ return {
168
+ ...node,
169
+ type: 'OptionalMemberExpression',
170
+ object: transformChainExpression(node.object),
171
+ };
172
+ // No default
173
+ }
174
+
175
+ return node;
176
+ }