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
@@ -36,13 +36,12 @@ export function createCloneCommentsToMutation(
36
36
  }
37
37
 
38
38
  export function performCloneCommentsToMutation(
39
- mutationContext: MutationContext,
39
+ _mutationContext: MutationContext,
40
40
  mutation: CloneCommentsToMutation,
41
41
  ): null {
42
42
  const newComments = [];
43
43
  for (const originalComment of getCommentsForNode(mutation.target)) {
44
44
  const comment = cloneCommentWithMarkers(originalComment);
45
- mutationContext.appendCommentToSource(comment);
46
45
  newComments.push(comment);
47
46
  }
48
47
  addCommentsToNode(mutation.destination, newComments);
@@ -43,6 +43,7 @@ function createInsertStatementMutation(side, target, nodesToInsert) {
43
43
  }
44
44
 
45
45
  function performInsertStatementMutation(mutationContext, mutation) {
46
+ mutationContext.assertNotDeleted(mutation.target, `Attempted to insert ${mutation.side} a deleted ${mutation.target.type} node. This likely means that you attempted to mutate around the target after it was deleted/replaced.`);
46
47
  const insertionParent = (0, _getStatementParent.getStatementParent)(mutation.target); // enforce that if we are inserting module declarations - they are being inserted in a valid location
47
48
 
48
49
  if (!(0, _isValidModuleDeclarationParent.isValidModuleDeclarationParent)(insertionParent.parent, mutation.nodesToInsert)) {
@@ -57,7 +58,7 @@ function performInsertStatementMutation(mutationContext, mutation) {
57
58
  switch (mutation.side) {
58
59
  case 'before':
59
60
  {
60
- parent[insertionParent.key] = (0, _arrayUtils.insertInArray)(parent[insertionParent.key], insertionParent.targetIndex - 1, mutation.nodesToInsert);
61
+ parent[insertionParent.key] = (0, _arrayUtils.insertInArray)(parent[insertionParent.key], insertionParent.targetIndex, mutation.nodesToInsert);
61
62
  break;
62
63
  }
63
64
 
@@ -85,5 +86,6 @@ function performInsertStatementMutation(mutationContext, mutation) {
85
86
  parent: insertionParent.parent
86
87
  });
87
88
  insertionParent.parent[insertionParent.key] = blockStatement;
89
+ statementToWrap.parent = blockStatement;
88
90
  return insertionParent.parent;
89
91
  }
@@ -46,6 +46,11 @@ export function performInsertStatementMutation(
46
46
  mutationContext: MutationContext,
47
47
  mutation: InsertStatementMutation,
48
48
  ): ESNode {
49
+ mutationContext.assertNotDeleted(
50
+ mutation.target,
51
+ `Attempted to insert ${mutation.side} a deleted ${mutation.target.type} node. This likely means that you attempted to mutate around the target after it was deleted/replaced.`,
52
+ );
53
+
49
54
  const insertionParent = getStatementParent(mutation.target);
50
55
 
51
56
  // enforce that if we are inserting module declarations - they are being inserted in a valid location
@@ -70,7 +75,7 @@ export function performInsertStatementMutation(
70
75
  case 'before': {
71
76
  parent[insertionParent.key] = insertInArray(
72
77
  parent[insertionParent.key],
73
- insertionParent.targetIndex - 1,
78
+ insertionParent.targetIndex,
74
79
  mutation.nodesToInsert,
75
80
  );
76
81
  break;
@@ -108,6 +113,7 @@ export function performInsertStatementMutation(
108
113
 
109
114
  (insertionParent.parent: interface {[string]: mixed})[insertionParent.key] =
110
115
  blockStatement;
116
+ statementToWrap.parent = blockStatement;
111
117
 
112
118
  return insertionParent.parent;
113
119
  }
@@ -46,8 +46,7 @@ function getRemovalParent(node) {
46
46
 
47
47
  switch (node.type) {
48
48
  // ClassMember
49
- case 'ClassProperty':
50
- case 'ClassPrivateProperty':
49
+ case 'PropertyDefinition':
51
50
  case 'MethodDefinition':
52
51
  assertParent('ClassBody');
53
52
  return 'body';
@@ -70,6 +69,14 @@ function getRemovalParent(node) {
70
69
  assertParent('FunctionTypeAnnotation');
71
70
  return 'params';
72
71
 
72
+ case 'JSXAttribute':
73
+ assertParent('JSXOpeningElement');
74
+ return 'attributes';
75
+
76
+ case 'ImportDeclaration':
77
+ assertParent('Program');
78
+ return 'body';
79
+
73
80
  case 'ObjectTypeCallProperty':
74
81
  assertParent('ObjectTypeAnnotation');
75
82
  return 'callProperties';
@@ -161,7 +168,7 @@ function getRemovalParent(node) {
161
168
  const idx = arr.indexOf(node);
162
169
 
163
170
  if (idx === -1) {
164
- throw new _Errors.InvalidRemovalError(`Could not find target in array of \`${parent.type}.${key}\`.`);
171
+ throw new _Errors.InvalidRemovalError(`Could not find target in array of \`${node.parent.type}.${key}\`.`);
165
172
  }
166
173
 
167
174
  return idx;
@@ -17,6 +17,8 @@ import type {
17
17
  EnumStringMember,
18
18
  FunctionParameter,
19
19
  FunctionTypeParam,
20
+ JSXAttribute,
21
+ ImportDeclaration,
20
22
  ObjectTypeCallProperty,
21
23
  ObjectTypeIndexer,
22
24
  ObjectTypeInternalSlot,
@@ -41,6 +43,8 @@ export type RemoveNodeMutation = $ReadOnly<{
41
43
  | EnumStringMember
42
44
  | FunctionParameter
43
45
  | FunctionTypeParam
46
+ | JSXAttribute
47
+ | ImportDeclaration
44
48
  | ObjectTypeCallProperty
45
49
  | ObjectTypeIndexer
46
50
  | ObjectTypeInternalSlot
@@ -102,8 +106,7 @@ function getRemovalParent(node: RemoveNodeMutation['node']): $ReadOnly<{
102
106
 
103
107
  switch (node.type) {
104
108
  // ClassMember
105
- case 'ClassProperty':
106
- case 'ClassPrivateProperty':
109
+ case 'PropertyDefinition':
107
110
  case 'MethodDefinition':
108
111
  assertParent('ClassBody');
109
112
  return 'body';
@@ -126,6 +129,14 @@ function getRemovalParent(node: RemoveNodeMutation['node']): $ReadOnly<{
126
129
  assertParent('FunctionTypeAnnotation');
127
130
  return 'params';
128
131
 
132
+ case 'JSXAttribute':
133
+ assertParent('JSXOpeningElement');
134
+ return 'attributes';
135
+
136
+ case 'ImportDeclaration':
137
+ assertParent('Program');
138
+ return 'body';
139
+
129
140
  case 'ObjectTypeCallProperty':
130
141
  assertParent('ObjectTypeAnnotation');
131
142
  return 'callProperties';
@@ -244,7 +255,7 @@ function getRemovalParent(node: RemoveNodeMutation['node']): $ReadOnly<{
244
255
  const idx = arr.indexOf(node);
245
256
  if (idx === -1) {
246
257
  throw new InvalidRemovalError(
247
- `Could not find target in array of \`${parent.type}.${key}\`.`,
258
+ `Could not find target in array of \`${node.parent.type}.${key}\`.`,
248
259
  );
249
260
  }
250
261
  return idx;
@@ -12,6 +12,8 @@ var _comments = require("../comments/comments");
12
12
 
13
13
  var _Errors = require("../Errors");
14
14
 
15
+ var _detachedNode = require("../../detachedNode");
16
+
15
17
  var _getVisitorKeys = require("../../getVisitorKeys");
16
18
 
17
19
  /**
@@ -71,7 +73,10 @@ function getParentKey(target) {
71
73
  }
72
74
  } else if (Array.isArray(parent[key])) {
73
75
  for (let i = 0; i < parent[key].length; i += 1) {
74
- if (parent[key][i] === target) {
76
+ const current = parent[key][i];
77
+ const originalNode = (0, _detachedNode.getOriginalNode)(current);
78
+
79
+ if (current === target || originalNode === target) {
75
80
  return {
76
81
  type: 'array',
77
82
  parent,
@@ -84,5 +89,5 @@ function getParentKey(target) {
84
89
  } // this shouldn't happen ever
85
90
 
86
91
 
87
- throw new _Errors.InvalidReplacementError(`Expected to find the ${target.type} as a direct child of the ${target.type}.`);
92
+ throw new _Errors.InvalidReplacementError(`Expected to find the ${target.type} as a direct child of the ${parent.type}.`);
88
93
  }
@@ -15,6 +15,7 @@ import type {DetachedNode} from '../../detachedNode';
15
15
  import {replaceInArray} from './utils/arrayUtils';
16
16
  import {moveCommentsToNewNode} from '../comments/comments';
17
17
  import {InvalidReplacementError} from '../Errors';
18
+ import {getOriginalNode} from '../../detachedNode';
18
19
  import {getVisitorKeys, isNode} from '../../getVisitorKeys';
19
20
 
20
21
  export type ReplaceNodeMutation = $ReadOnly<{
@@ -99,7 +100,9 @@ function getParentKey(target: ESNode): $ReadOnly<
99
100
  }
100
101
  } else if (Array.isArray(parent[key])) {
101
102
  for (let i = 0; i < parent[key].length; i += 1) {
102
- if (parent[key][i] === target) {
103
+ const current = parent[key][i];
104
+ const originalNode = getOriginalNode(current);
105
+ if (current === target || originalNode === target) {
103
106
  return {type: 'array', parent, key, targetIndex: i};
104
107
  }
105
108
  }
@@ -108,6 +111,6 @@ function getParentKey(target: ESNode): $ReadOnly<
108
111
 
109
112
  // this shouldn't happen ever
110
113
  throw new InvalidReplacementError(
111
- `Expected to find the ${target.type} as a direct child of the ${target.type}.`,
114
+ `Expected to find the ${target.type} as a direct child of the ${parent.type}.`,
112
115
  );
113
116
  }
@@ -19,11 +19,14 @@ 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
  }>;
@@ -16,14 +16,28 @@ exports.replaceInArray = replaceInArray;
16
16
  *
17
17
  * @format
18
18
  */
19
+ function assertArrayBounds(array, index) {
20
+ if (index < 0 || index >= array.length) {
21
+ throw new Error(`Invalid Mutation: Tried to mutate an elements array with an out of bounds index. Index: ${index}, Array Size: ${array.length}`);
22
+ }
23
+ }
24
+
19
25
  function insertInArray(array, index, elements) {
26
+ if (index === array.length) {
27
+ // Support the insert at end of array case.
28
+ return array.concat(elements);
29
+ }
30
+
31
+ assertArrayBounds(array, index);
20
32
  return array.slice(0, index).concat(elements).concat(array.slice(index));
21
33
  }
22
34
 
23
35
  function removeFromArray(array, index) {
36
+ assertArrayBounds(array, index);
24
37
  return [...array.slice(0, index), ...array.slice(index + 1)];
25
38
  }
26
39
 
27
40
  function replaceInArray(array, index, elements) {
41
+ assertArrayBounds(array, index);
28
42
  return array.slice(0, index).concat(elements).concat(array.slice(index + 1));
29
43
  }
@@ -8,11 +8,24 @@
8
8
  * @format
9
9
  */
10
10
 
11
+ function assertArrayBounds<T>(array: $ReadOnlyArray<T>, index: number): void {
12
+ if (index < 0 || index >= array.length) {
13
+ throw new Error(
14
+ `Invalid Mutation: Tried to mutate an elements array with an out of bounds index. Index: ${index}, Array Size: ${array.length}`,
15
+ );
16
+ }
17
+ }
18
+
11
19
  export function insertInArray<T>(
12
20
  array: $ReadOnlyArray<T>,
13
21
  index: number,
14
22
  elements: $ReadOnlyArray<T>,
15
23
  ): Array<T> {
24
+ if (index === array.length) {
25
+ // Support the insert at end of array case.
26
+ return array.concat(elements);
27
+ }
28
+ assertArrayBounds(array, index);
16
29
  return array.slice(0, index).concat(elements).concat(array.slice(index));
17
30
  }
18
31
 
@@ -20,6 +33,7 @@ export function removeFromArray<T>(
20
33
  array: $ReadOnlyArray<T>,
21
34
  index: number,
22
35
  ): Array<T> {
36
+ assertArrayBounds(array, index);
23
37
  return [...array.slice(0, index), ...array.slice(index + 1)];
24
38
  }
25
39
 
@@ -28,6 +42,7 @@ export function replaceInArray<T>(
28
42
  index: number,
29
43
  elements: $ReadOnlyArray<T>,
30
44
  ): Array<T> {
45
+ assertArrayBounds(array, index);
31
46
  return array
32
47
  .slice(0, index)
33
48
  .concat(elements)
@@ -33,16 +33,26 @@ function transform(originalCode, visitors, prettierOptions = {}) {
33
33
 
34
34
  if (!astWasMutated) {
35
35
  return originalCode;
36
- } // prettier fully expects the parent pointers are NOT set and
37
- // certain cases can crash due to prettier infinite-looping
38
- // whilst naively traversing the parent property
39
- // https://github.com/prettier/prettier/issues/11793
40
-
36
+ }
41
37
 
42
38
  _SimpleTraverser.SimpleTraverser.traverse(ast, {
43
39
  enter(node) {
40
+ // prettier fully expects the parent pointers are NOT set and
41
+ // certain cases can crash due to prettier infinite-looping
42
+ // whilst naively traversing the parent property
43
+ // https://github.com/prettier/prettier/issues/11793
44
44
  // $FlowExpectedError[cannot-write]
45
- delete node.parent;
45
+ delete node.parent; // prettier currently relies on the AST being in the old-school, deprecated AST format for optional chaining
46
+ // so we have to apply their transform to our AST so it can actually format it.
47
+
48
+ if (node.type === 'ChainExpression') {
49
+ const newNode = transformChainExpression(node.expression); // $FlowExpectedError[cannot-write]
50
+
51
+ delete node.expression; // $FlowExpectedError[prop-missing]
52
+ // $FlowExpectedError[cannot-write]
53
+
54
+ Object.assign(node, newNode);
55
+ }
46
56
  },
47
57
 
48
58
  leave() {}
@@ -62,4 +72,26 @@ function transform(originalCode, visitors, prettierOptions = {}) {
62
72
  }
63
73
 
64
74
  });
75
+ } // https://github.com/prettier/prettier/blob/d962466a828f8ef51435e3e8840178d90b7ec6cd/src/language-js/parse/postprocess/index.js#L161-L182
76
+
77
+
78
+ function transformChainExpression(node) {
79
+ switch (node.type) {
80
+ case 'CallExpression':
81
+ // $FlowExpectedError[cannot-write]
82
+ node.type = 'OptionalCallExpression'; // $FlowExpectedError[cannot-write]
83
+
84
+ node.callee = transformChainExpression(node.callee);
85
+ break;
86
+
87
+ case 'MemberExpression':
88
+ // $FlowExpectedError[cannot-write]
89
+ node.type = 'OptionalMemberExpression'; // $FlowExpectedError[cannot-write]
90
+
91
+ node.object = transformChainExpression(node.object);
92
+ break;
93
+ // No default
94
+ }
95
+
96
+ return node;
65
97
  }
@@ -10,14 +10,15 @@
10
10
 
11
11
  'use strict';
12
12
 
13
+ import type {ESNode} from 'hermes-estree';
13
14
  import type {Visitor} from '../traverse/traverse';
14
- import type {TransformContext} from './TransformContext';
15
+ import type {TransformContextAdditions} from './TransformContext';
15
16
 
16
17
  import * as prettier from 'prettier';
17
18
  import {getTransformedAST} from './getTransformedAST';
18
19
  import {SimpleTraverser} from '../traverse/SimpleTraverser';
19
20
 
20
- export type TransformVisitor = Visitor<TransformContext>;
21
+ export type TransformVisitor = Visitor<TransformContextAdditions>;
21
22
 
22
23
  export function transform(
23
24
  originalCode: string,
@@ -33,14 +34,25 @@ export function transform(
33
34
  return originalCode;
34
35
  }
35
36
 
36
- // prettier fully expects the parent pointers are NOT set and
37
- // certain cases can crash due to prettier infinite-looping
38
- // whilst naively traversing the parent property
39
- // https://github.com/prettier/prettier/issues/11793
40
37
  SimpleTraverser.traverse(ast, {
41
38
  enter(node) {
39
+ // prettier fully expects the parent pointers are NOT set and
40
+ // certain cases can crash due to prettier infinite-looping
41
+ // whilst naively traversing the parent property
42
+ // https://github.com/prettier/prettier/issues/11793
42
43
  // $FlowExpectedError[cannot-write]
43
44
  delete node.parent;
45
+
46
+ // prettier currently relies on the AST being in the old-school, deprecated AST format for optional chaining
47
+ // so we have to apply their transform to our AST so it can actually format it.
48
+ if (node.type === 'ChainExpression') {
49
+ const newNode = transformChainExpression(node.expression);
50
+ // $FlowExpectedError[cannot-write]
51
+ delete node.expression;
52
+ // $FlowExpectedError[prop-missing]
53
+ // $FlowExpectedError[cannot-write]
54
+ Object.assign(node, newNode);
55
+ }
44
56
  },
45
57
  leave() {},
46
58
  });
@@ -62,3 +74,25 @@ export function transform(
62
74
  },
63
75
  );
64
76
  }
77
+
78
+ // https://github.com/prettier/prettier/blob/d962466a828f8ef51435e3e8840178d90b7ec6cd/src/language-js/parse/postprocess/index.js#L161-L182
79
+ function transformChainExpression(node: ESNode) {
80
+ switch (node.type) {
81
+ case 'CallExpression':
82
+ // $FlowExpectedError[cannot-write]
83
+ node.type = 'OptionalCallExpression';
84
+ // $FlowExpectedError[cannot-write]
85
+ node.callee = transformChainExpression(node.callee);
86
+ break;
87
+
88
+ case 'MemberExpression':
89
+ // $FlowExpectedError[cannot-write]
90
+ node.type = 'OptionalMemberExpression';
91
+ // $FlowExpectedError[cannot-write]
92
+ node.object = transformChainExpression(node.object);
93
+ break;
94
+ // No default
95
+ }
96
+
97
+ return node;
98
+ }
@@ -34,7 +34,7 @@ type ParsedSelector = $ReadOnly<{
34
34
 
35
35
  const ESQUERY_OPTIONS: ESQueryOptions = Object.freeze({
36
36
  visitorKeys: VisitorKeys,
37
- fallback: node => {
37
+ fallback: (node: ESNode) => {
38
38
  throw new Error(`No visitor keys found for node type "${node.type}".`);
39
39
  },
40
40
  });
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.traverse = traverse;
7
7
  exports.traverseWithContext = traverseWithContext;
8
8
 
9
+ var _codeFrame = require("@babel/code-frame");
10
+
9
11
  var _NodeEventGenerator = require("./NodeEventGenerator");
10
12
 
11
13
  var _SafeEmitter = require("./SafeEmitter");
@@ -28,7 +30,7 @@ var _SimpleTraverser = require("./SimpleTraverser");
28
30
  * @param scopeManager the eslint-scope compatible scope manager instance calculated using the ast
29
31
  * @param additionalContext a callback function which returns additional context members to add to the context provided to the visitor
30
32
  */
31
- function traverseWithContext(ast, scopeManager, additionalContext, visitor) {
33
+ function traverseWithContext(code, ast, scopeManager, additionalContext, visitor) {
32
34
  const emitter = new _SafeEmitter.SafeEmitter();
33
35
  const nodeQueue = [];
34
36
  let currentNode = ast; // set parent pointers and build up the traversal queue
@@ -72,6 +74,28 @@ function traverseWithContext(ast, scopeManager, additionalContext, visitor) {
72
74
  };
73
75
 
74
76
  const traversalContextBase = Object.freeze({
77
+ buildCodeFrame: (node, message) => {
78
+ // babel uses 1-indexed columns
79
+ const locForBabel = {
80
+ start: {
81
+ line: node.loc.start.line,
82
+ column: node.loc.start.column + 1
83
+ },
84
+ end: {
85
+ line: node.loc.end.line,
86
+ column: node.loc.end.column + 1
87
+ }
88
+ };
89
+ return (0, _codeFrame.codeFrameColumns)(code, locForBabel, {
90
+ linesAbove: 0,
91
+ linesBelow: 0,
92
+ highlightCode: process.env.NODE_ENV !== 'test',
93
+ message: message
94
+ });
95
+ },
96
+ buildSimpleCodeFrame: (node, message) => {
97
+ return `[${node.type}:${node.loc.start.line}:${node.loc.start.column}] ${message}`;
98
+ },
75
99
  getDeclaredVariables: node => scopeManager.getDeclaredVariables(node),
76
100
  getBinding: name => {
77
101
  let currentScope = getScope();
@@ -122,6 +146,6 @@ function traverseWithContext(ast, scopeManager, additionalContext, visitor) {
122
146
  });
123
147
  }
124
148
 
125
- function traverse(ast, scopeManager, visitor) {
126
- traverseWithContext(ast, scopeManager, () => {}, visitor);
149
+ function traverse(code, ast, scopeManager, visitor) {
150
+ traverseWithContext(code, ast, scopeManager, () => {}, visitor);
127
151
  }
@@ -12,11 +12,12 @@ import type {ESNode, ESQueryNodeSelectors, Program} from 'hermes-estree';
12
12
  import type {ScopeManager, Scope, Variable} from 'hermes-eslint';
13
13
  import type {EmitterListener} from './SafeEmitter';
14
14
 
15
+ import {codeFrameColumns} from '@babel/code-frame';
15
16
  import {NodeEventGenerator} from './NodeEventGenerator';
16
17
  import {SafeEmitter} from './SafeEmitter';
17
18
  import {SimpleTraverser} from './SimpleTraverser';
18
19
 
19
- export type TraversalContext<T = void> = $ReadOnly<{
20
+ export type TraversalContextBase = $ReadOnly<{
20
21
  /**
21
22
  * Gets the variables that were declared by the given node.
22
23
  */
@@ -31,13 +32,36 @@ export type TraversalContext<T = void> = $ReadOnly<{
31
32
  * Defaults to the currently traversed node.
32
33
  */
33
34
  getScope: (node?: ESNode) => Scope,
34
-
35
+ /**
36
+ * Creates a full code frame for the node along with the message.
37
+ *
38
+ * i.e. `context.buildCodeFrame(node, 'foo')` will create a string like:
39
+ * ```
40
+ * 56 | function () {
41
+ * | ^^^^^^^^^^^^^
42
+ * 57 | }.bind(this)
43
+ * | ^^ foo
44
+ * ```
45
+ */
46
+ buildCodeFrame: (node: ESNode, message: string) => string,
47
+ /**
48
+ * Creates a simple code frame for the node along with the message.
49
+ * Use this if you want a condensed marker for your message.
50
+ *
51
+ * i.e. `context.logWithNode(node, 'foo')` will create a string like:
52
+ * ```
53
+ * [FunctionExpression:56:44] foo
54
+ * ```
55
+ * (where 56:44 represents L56, Col44)
56
+ */
57
+ buildSimpleCodeFrame: (node: ESNode, message: string) => string,
58
+ }>;
59
+ export type TraversalContext<T> = $ReadOnly<{
60
+ ...TraversalContextBase,
35
61
  ...T,
36
62
  }>;
37
63
 
38
- export type Visitor<T = void> = (
39
- context: TraversalContext<T>,
40
- ) => ESQueryNodeSelectors;
64
+ export type Visitor<T> = (context: TraversalContext<T>) => ESQueryNodeSelectors;
41
65
 
42
66
  /**
43
67
  * Traverse the AST with additional context members provided by `additionalContext`.
@@ -45,10 +69,11 @@ export type Visitor<T = void> = (
45
69
  * @param scopeManager the eslint-scope compatible scope manager instance calculated using the ast
46
70
  * @param additionalContext a callback function which returns additional context members to add to the context provided to the visitor
47
71
  */
48
- export function traverseWithContext<T = void>(
72
+ export function traverseWithContext<T = TraversalContextBase>(
73
+ code: string,
49
74
  ast: Program,
50
75
  scopeManager: ScopeManager,
51
- additionalContext: (TraversalContext<void>) => T,
76
+ additionalContext: TraversalContextBase => T,
52
77
  visitor: Visitor<T>,
53
78
  ): void {
54
79
  const emitter = new SafeEmitter();
@@ -86,11 +111,36 @@ export function traverseWithContext<T = void>(
86
111
  return scopeManager.scopes[0];
87
112
  };
88
113
 
89
- const traversalContextBase: TraversalContext<void> = Object.freeze({
114
+ const traversalContextBase: TraversalContextBase = Object.freeze({
115
+ buildCodeFrame: (node: ESNode, message: string): string => {
116
+ // babel uses 1-indexed columns
117
+ const locForBabel = {
118
+ start: {
119
+ line: node.loc.start.line,
120
+ column: node.loc.start.column + 1,
121
+ },
122
+ end: {
123
+ line: node.loc.end.line,
124
+ column: node.loc.end.column + 1,
125
+ },
126
+ };
127
+ return codeFrameColumns(code, locForBabel, {
128
+ linesAbove: 0,
129
+ linesBelow: 0,
130
+ highlightCode: process.env.NODE_ENV !== 'test',
131
+ message: message,
132
+ });
133
+ },
134
+
135
+ buildSimpleCodeFrame: (node: ESNode, message: string): string => {
136
+ return `[${node.type}:${node.loc.start.line}:${node.loc.start.column}] ${message}`;
137
+ },
138
+
90
139
  getDeclaredVariables: (node: ESNode) =>
91
140
  scopeManager.getDeclaredVariables(node),
141
+
92
142
  getBinding: (name: string) => {
93
- let currentScope = getScope();
143
+ let currentScope: null | Scope = getScope();
94
144
 
95
145
  while (currentScope != null) {
96
146
  for (const variable of currentScope.variables) {
@@ -103,8 +153,10 @@ export function traverseWithContext<T = void>(
103
153
 
104
154
  return null;
105
155
  },
156
+
106
157
  getScope,
107
158
  });
159
+
108
160
  const traversalContext: TraversalContext<T> = Object.freeze({
109
161
  ...traversalContextBase,
110
162
  ...additionalContext(traversalContextBase),
@@ -141,9 +193,10 @@ export function traverseWithContext<T = void>(
141
193
  }
142
194
 
143
195
  export function traverse(
196
+ code: string,
144
197
  ast: Program,
145
198
  scopeManager: ScopeManager,
146
199
  visitor: Visitor<void>,
147
200
  ): void {
148
- traverseWithContext(ast, scopeManager, () => {}, visitor);
201
+ traverseWithContext(code, ast, scopeManager, () => {}, visitor);
149
202
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermes-transform",
3
- "version": "0.6.0",
3
+ "version": "0.9.0",
4
4
  "description": "Tools built on top of Hermes-ESTree to enable codebase transformation",
5
5
  "main": "dist/index.js",
6
6
  "license": "MIT",
@@ -11,13 +11,18 @@
11
11
  "dependencies": {
12
12
  "@babel/code-frame": "^7.16.0",
13
13
  "esquery": "^1.4.0",
14
- "hermes-eslint": "0.6.0",
15
- "hermes-estree": "0.6.0"
14
+ "flow-enums-runtime": "^0.0.6",
15
+ "hermes-eslint": "0.9.0",
16
+ "hermes-estree": "0.9.0"
16
17
  },
17
18
  "peerDependencies": {
18
19
  "prettier": "^2.4.1"
19
20
  },
20
21
  "files": [
21
- "dist"
22
+ "dist",
23
+ "LICENCE",
24
+ "ESLINT_LICENCE",
25
+ "PRETTIER_LICENCE",
26
+ "README.md"
22
27
  ]
23
28
  }