hermes-transform 0.6.0 → 0.7.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.
@@ -23,9 +23,17 @@ import {
23
23
  // $FlowExpectedError[untyped-import]
24
24
  addTrailingComment as untypedAddTrailingComment,
25
25
  } from './prettier/common/util';
26
+ import {EOL} from 'os';
26
27
 
27
28
  export type Options = $ReadOnly<{}>;
28
29
 
30
+ export enum CommentPlacement {
31
+ LEADING_OWN_LINE,
32
+ LEADING_INLINE,
33
+ TRAILING_OWN_LINE,
34
+ TRAILING_INLINE,
35
+ }
36
+
29
37
  export function attachComments(
30
38
  comments: $ReadOnlyArray<Comment>,
31
39
  ast: Program,
@@ -80,18 +88,23 @@ export function isTrailingComment(comment: Comment): boolean {
80
88
  return comment.trailing === true;
81
89
  }
82
90
 
83
- export function addLeadingComment(
84
- node: ESNode | DetachedNode<ESNode>,
85
- comment: Comment,
86
- ): void {
87
- untypedAddLeadingComment(node, comment);
88
- }
89
-
90
- export function addTrailingComment(
91
+ export function addComment(
91
92
  node: ESNode | DetachedNode<ESNode>,
92
93
  comment: Comment,
94
+ placement: CommentPlacement,
93
95
  ): void {
94
- untypedAddTrailingComment(node, comment);
96
+ switch (placement) {
97
+ case CommentPlacement.LEADING_OWN_LINE:
98
+ case CommentPlacement.LEADING_INLINE: {
99
+ untypedAddLeadingComment(node, comment);
100
+ break;
101
+ }
102
+ case CommentPlacement.TRAILING_OWN_LINE:
103
+ case CommentPlacement.TRAILING_INLINE: {
104
+ untypedAddTrailingComment(node, comment);
105
+ break;
106
+ }
107
+ }
95
108
  }
96
109
 
97
110
  export function cloneComment<T: Comment>(comment: T): T {
@@ -116,30 +129,94 @@ export function cloneCommentWithMarkers<T: Comment>(comment: T): T {
116
129
  };
117
130
  }
118
131
 
119
- export function appendCommentToSource(code: string, comment: Comment): string {
120
- // prettier slices comments directly from the source code when printing
121
- // https://github.com/prettier/prettier/blob/5f0ee39fa03532c85bd1c35291450fe7ac3667b3/src/language-js/print/comment.js#L15-L20
122
- // this means that we need to have any appended comments directly in the
123
- // source code or else prettier will slice nothing and bork up the transform
132
+ function getFirstNewlineIndex(code: string): number {
133
+ return code.search(/\r\n|\n|\r/);
134
+ }
135
+
136
+ function getFirstNonWhitespaceIndex(code: string): number {
137
+ return code.search(/\S/);
138
+ }
124
139
 
125
- let commentText = comment.value;
140
+ export function appendCommentToSource(
141
+ code: string,
142
+ comment: Comment,
143
+ placement: CommentPlacement,
144
+ ): string {
145
+ let newCode = code;
126
146
  switch (comment.type) {
127
- case 'Block':
128
- commentText = `/*${commentText}*/`;
147
+ case 'Block': {
148
+ // Prettier decides if a newline is necessary between the comment and its node by looking
149
+ // to see if a newline seperates them in the source text. We can trick prettier into
150
+ // formatting how we want for new comments by placing the range such that a newline
151
+ // will (OWN_LINE) or will not (INLINE) be found when searching from the specified range
152
+ // position.
153
+ switch (placement) {
154
+ case CommentPlacement.LEADING_OWN_LINE:
155
+ case CommentPlacement.TRAILING_OWN_LINE: {
156
+ // Since we always want a line break we need to ensure a newline is found when
157
+ // searching out from either side of the comment range.
158
+ let firstNewline = getFirstNewlineIndex(code);
159
+ if (firstNewline === -1) {
160
+ // No newline in file, lets add one.
161
+ newCode += EOL;
162
+ firstNewline = newCode.length;
163
+ }
164
+ // Prettier only uses these ranges for detecting whitespace, so this nonsensical
165
+ // range is safe.
166
+ // $FlowExpectedError[cannot-write]
167
+ comment.range = [firstNewline + 1, firstNewline];
168
+ break;
169
+ }
170
+ case CommentPlacement.LEADING_INLINE:
171
+ case CommentPlacement.TRAILING_INLINE: {
172
+ // Since we don't want a line break we need to ensure a non whitespace char is
173
+ // always found before a newline when searching out from either side of the
174
+ // comment range.
175
+ let firstNonWhitespace = getFirstNonWhitespaceIndex(code);
176
+ if (firstNonWhitespace === -1) {
177
+ // No non whitespace chars in file, lets add an identifiable statement for prettier to find.
178
+ newCode += '$FORCE_INLINE_ON_EMPTY_FILE_TOKEN$;';
179
+ firstNonWhitespace = newCode.length;
180
+ break;
181
+ }
182
+
183
+ // $FlowExpectedError[cannot-write]
184
+ comment.range = [firstNonWhitespace + 1, firstNonWhitespace];
185
+ break;
186
+ }
187
+ }
129
188
  break;
130
- case 'Line':
131
- commentText = `//${commentText}`;
189
+ }
190
+ case 'Line': {
191
+ // For `Line` comments prettier slices comments directly from the source code when printing
192
+ // https://github.com/prettier/prettier/blob/5f0ee39fa03532c85bd1c35291450fe7ac3667b3/src/language-js/print/comment.js#L15-L20
193
+ // this means that we need to have any appended comments directly in the
194
+ // source code or else prettier will slice nothing and bork up the transform
195
+ const commentText = `//${comment.value}`;
196
+
197
+ const lastChar = newCode[newCode.length - 1];
198
+ if (lastChar !== '\n' && lastChar !== '\r') {
199
+ newCode += EOL;
200
+ }
201
+
202
+ // Line comments cannot be inline before a node so we only place trailing Line comments inline.
203
+ if (placement === CommentPlacement.TRAILING_INLINE) {
204
+ // Prettier determines an "end of line" comment by walking backwards from
205
+ // the comment start range through the source code to see if it finds a non
206
+ // newline token. In order to trick prettier for new comments we need to
207
+ // insert fake source code for it to find.
208
+ newCode += '$FORCE_END_OF_LINE_COMMENT_TOKEN$;';
209
+ }
210
+ const start = newCode.length;
211
+ newCode += commentText;
212
+ const end = newCode.length;
213
+
214
+ // $FlowExpectedError[cannot-write]
215
+ comment.range = [start, end];
216
+
132
217
  break;
218
+ }
133
219
  }
134
220
 
135
- let newCode = code;
136
- newCode += '\n';
137
- const start = newCode.length;
138
- newCode += commentText;
139
- const end = newCode.length;
140
-
141
- // $FlowExpectedError[cannot-write]
142
- comment.range = [start, end];
143
-
144
221
  return newCode;
145
222
  }
@@ -26,9 +26,7 @@ var _TransformContext = require("./TransformContext");
26
26
 
27
27
  var _comments = require("./comments/comments");
28
28
 
29
- var _AddLeadingComments = require("./mutations/AddLeadingComments");
30
-
31
- var _AddTrailingComments = require("./mutations/AddTrailingComments");
29
+ var _AddComments = require("./mutations/AddComments");
32
30
 
33
31
  var _CloneCommentsTo = require("./mutations/CloneCommentsTo");
34
32
 
@@ -55,8 +53,8 @@ function getTransformedAST(code, visitors) {
55
53
 
56
54
  (0, _comments.attachComments)(ast.comments, ast, code); // traverse the AST and colllect the mutations
57
55
 
58
- const transformContext = (0, _TransformContext.getTransformContext)(code);
59
- (0, _traverse.traverseWithContext)(ast, scopeManager, () => transformContext, visitors); // apply the mutations to the AST
56
+ const transformContext = (0, _TransformContext.getTransformContext)();
57
+ (0, _traverse.traverseWithContext)(code, ast, scopeManager, () => transformContext, visitors); // apply the mutations to the AST
60
58
 
61
59
  const mutationContext = new _MutationContext.MutationContext(code);
62
60
  const removeCommentMutations = [];
@@ -96,14 +94,9 @@ function getTransformedAST(code, visitors) {
96
94
  return null;
97
95
  }
98
96
 
99
- case 'addLeadingComments':
100
- {
101
- return (0, _AddLeadingComments.performAddLeadingCommentsMutation)(mutationContext, mutation);
102
- }
103
-
104
- case 'addTrailingComments':
97
+ case 'addComments':
105
98
  {
106
- return (0, _AddTrailingComments.performAddTrailingCommentsMutation)(mutationContext, mutation);
99
+ return (0, _AddComments.performAddCommentsMutation)(mutationContext, mutation);
107
100
  }
108
101
 
109
102
  case 'cloneCommentsTo':
@@ -11,8 +11,7 @@
11
11
  'use strict';
12
12
 
13
13
  import type {ESNode, Program} from 'hermes-estree';
14
- import type {Visitor} from '../traverse/traverse';
15
- import type {TransformContext} from './TransformContext';
14
+ import type {TransformVisitor} from './transform';
16
15
  import type {RemoveCommentMutation} from './mutations/RemoveComment';
17
16
 
18
17
  import {parseForESLint} from 'hermes-eslint';
@@ -21,8 +20,7 @@ import {traverseWithContext} from '../traverse/traverse';
21
20
  import {MutationContext} from './MutationContext';
22
21
  import {getTransformContext} from './TransformContext';
23
22
  import {attachComments} from './comments/comments';
24
- import {performAddLeadingCommentsMutation} from './mutations/AddLeadingComments';
25
- import {performAddTrailingCommentsMutation} from './mutations/AddTrailingComments';
23
+ import {performAddCommentsMutation} from './mutations/AddComments';
26
24
  import {performCloneCommentsToMutation} from './mutations/CloneCommentsTo';
27
25
  import {performInsertStatementMutation} from './mutations/InsertStatement';
28
26
  import {performRemoveCommentMutations} from './mutations/RemoveComment';
@@ -33,7 +31,7 @@ import {performReplaceStatementWithManyMutation} from './mutations/ReplaceStatem
33
31
 
34
32
  export function getTransformedAST(
35
33
  code: string,
36
- visitors: Visitor<TransformContext>,
34
+ visitors: TransformVisitor,
37
35
  ): {
38
36
  ast: Program,
39
37
  astWasMutated: boolean,
@@ -48,8 +46,14 @@ export function getTransformedAST(
48
46
  attachComments(ast.comments, ast, code);
49
47
 
50
48
  // traverse the AST and colllect the mutations
51
- const transformContext = getTransformContext(code);
52
- traverseWithContext(ast, scopeManager, () => transformContext, visitors);
49
+ const transformContext = getTransformContext();
50
+ traverseWithContext(
51
+ code,
52
+ ast,
53
+ scopeManager,
54
+ () => transformContext,
55
+ visitors,
56
+ );
53
57
 
54
58
  // apply the mutations to the AST
55
59
  const mutationContext = new MutationContext(code);
@@ -88,12 +92,8 @@ export function getTransformedAST(
88
92
  return null;
89
93
  }
90
94
 
91
- case 'addLeadingComments': {
92
- return performAddLeadingCommentsMutation(mutationContext, mutation);
93
- }
94
-
95
- case 'addTrailingComments': {
96
- return performAddTrailingCommentsMutation(mutationContext, mutation);
95
+ case 'addComments': {
96
+ return performAddCommentsMutation(mutationContext, mutation);
97
97
  }
98
98
 
99
99
  case 'cloneCommentsTo': {
@@ -3,8 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.createAddLeadingCommentsMutation = createAddLeadingCommentsMutation;
7
- exports.performAddLeadingCommentsMutation = performAddLeadingCommentsMutation;
6
+ exports.createAddCommentsMutation = createAddCommentsMutation;
7
+ exports.performAddCommentsMutation = performAddCommentsMutation;
8
8
 
9
9
  var _comments = require("../comments/comments");
10
10
 
@@ -17,23 +17,26 @@ var _comments = require("../comments/comments");
17
17
  *
18
18
  * @format
19
19
  */
20
- function createAddLeadingCommentsMutation(node, comments) {
20
+ function createAddCommentsMutation(node, comments) {
21
21
  if (comments.length === 0) {
22
22
  return null;
23
23
  }
24
24
 
25
25
  return {
26
- type: 'addLeadingComments',
26
+ type: 'addComments',
27
27
  comments,
28
28
  node
29
29
  };
30
30
  }
31
31
 
32
- function performAddLeadingCommentsMutation(mutationContext, mutation) {
33
- for (const originalComment of mutation.comments) {
32
+ function performAddCommentsMutation(mutationContext, mutation) {
33
+ for (const {
34
+ comment: originalComment,
35
+ placement
36
+ } of mutation.comments) {
34
37
  const comment = (0, _comments.cloneComment)(originalComment);
35
- mutationContext.appendCommentToSource(comment);
36
- (0, _comments.addLeadingComment)(mutation.node, comment);
38
+ mutationContext.appendCommentToSource(comment, placement);
39
+ (0, _comments.addComment)(mutation.node, comment, placement);
37
40
  }
38
41
 
39
42
  return null;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ */
10
+
11
+ import type {Comment, ESNode} from 'hermes-estree';
12
+ import type {DetachedNode} from '../../detachedNode';
13
+ import type {MutationContext} from '../MutationContext';
14
+ import type {CommentPlacement} from '../comments/comments';
15
+
16
+ import {addComment, cloneComment} from '../comments/comments';
17
+
18
+ export type AddCommentsMutation = $ReadOnly<{
19
+ type: 'addComments',
20
+ comments: $ReadOnlyArray<{comment: Comment, placement: CommentPlacement}>,
21
+ node: ESNode | DetachedNode<ESNode>,
22
+ }>;
23
+
24
+ export function createAddCommentsMutation(
25
+ node: AddCommentsMutation['node'],
26
+ comments: AddCommentsMutation['comments'],
27
+ ): ?AddCommentsMutation {
28
+ if (comments.length === 0) {
29
+ return null;
30
+ }
31
+
32
+ return {
33
+ type: 'addComments',
34
+ comments,
35
+ node,
36
+ };
37
+ }
38
+
39
+ export function performAddCommentsMutation(
40
+ mutationContext: MutationContext,
41
+ mutation: AddCommentsMutation,
42
+ ): null {
43
+ for (const {comment: originalComment, placement} of mutation.comments) {
44
+ const comment = cloneComment(originalComment);
45
+ mutationContext.appendCommentToSource(comment, placement);
46
+ addComment(mutation.node, comment, placement);
47
+ }
48
+
49
+ return null;
50
+ }
@@ -25,12 +25,11 @@ function createCloneCommentsToMutation(target, destination) {
25
25
  };
26
26
  }
27
27
 
28
- function performCloneCommentsToMutation(mutationContext, mutation) {
28
+ function performCloneCommentsToMutation(_mutationContext, mutation) {
29
29
  const newComments = [];
30
30
 
31
31
  for (const originalComment of (0, _comments.getCommentsForNode)(mutation.target)) {
32
32
  const comment = (0, _comments.cloneCommentWithMarkers)(originalComment);
33
- mutationContext.appendCommentToSource(comment);
34
33
  newComments.push(comment);
35
34
  }
36
35
 
@@ -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);
@@ -57,7 +57,7 @@ function performInsertStatementMutation(mutationContext, mutation) {
57
57
  switch (mutation.side) {
58
58
  case 'before':
59
59
  {
60
- parent[insertionParent.key] = (0, _arrayUtils.insertInArray)(parent[insertionParent.key], insertionParent.targetIndex - 1, mutation.nodesToInsert);
60
+ parent[insertionParent.key] = (0, _arrayUtils.insertInArray)(parent[insertionParent.key], insertionParent.targetIndex, mutation.nodesToInsert);
61
61
  break;
62
62
  }
63
63
 
@@ -70,7 +70,7 @@ export function performInsertStatementMutation(
70
70
  case 'before': {
71
71
  parent[insertionParent.key] = insertInArray(
72
72
  parent[insertionParent.key],
73
- insertionParent.targetIndex - 1,
73
+ insertionParent.targetIndex,
74
74
  mutation.nodesToInsert,
75
75
  );
76
76
  break;
@@ -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)
@@ -11,13 +11,13 @@
11
11
  'use strict';
12
12
 
13
13
  import type {Visitor} from '../traverse/traverse';
14
- import type {TransformContext} from './TransformContext';
14
+ import type {TransformContextAdditions} from './TransformContext';
15
15
 
16
16
  import * as prettier from 'prettier';
17
17
  import {getTransformedAST} from './getTransformedAST';
18
18
  import {SimpleTraverser} from '../traverse/SimpleTraverser';
19
19
 
20
- export type TransformVisitor = Visitor<TransformContext>;
20
+ export type TransformVisitor = Visitor<TransformContextAdditions>;
21
21
 
22
22
  export function transform(
23
23
  originalCode: string,
@@ -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,9 +111,34 @@ 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
143
  let currentScope = getScope();
94
144
 
@@ -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
  }