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.
- package/ESLINT_LICENCE +19 -0
- package/PRETTIER_LICENCE +7 -0
- package/dist/detachedNode.js +96 -20
- package/dist/detachedNode.js.flow +105 -17
- package/dist/generated/TransformCloneSignatures.js.flow +33 -66
- package/dist/generated/TransformModifySignatures.js.flow +1113 -0
- package/dist/generated/TransformReplaceSignatures.js.flow +19 -40
- package/dist/generated/node-types.js +638 -905
- package/dist/generated/node-types.js.flow +998 -1256
- package/dist/generated/special-case-node-types.js +71 -77
- package/dist/generated/special-case-node-types.js.flow +104 -88
- package/dist/index.js.flow +2 -1
- package/dist/transform/MutationContext.js +2 -2
- package/dist/transform/MutationContext.js.flow +3 -2
- package/dist/transform/TransformContext.js +56 -43
- package/dist/transform/TransformContext.js.flow +154 -95
- package/dist/transform/comments/comments.js +133 -28
- package/dist/transform/comments/comments.js.flow +125 -28
- package/dist/transform/getTransformedAST.js +20 -12
- package/dist/transform/getTransformedAST.js.flow +32 -14
- package/dist/transform/mutations/{AddLeadingComments.js → AddComments.js} +11 -8
- package/dist/transform/mutations/AddComments.js.flow +50 -0
- package/dist/transform/mutations/CloneCommentsTo.js +1 -2
- package/dist/transform/mutations/CloneCommentsTo.js.flow +1 -2
- package/dist/transform/mutations/InsertStatement.js +3 -1
- package/dist/transform/mutations/InsertStatement.js.flow +7 -1
- package/dist/transform/mutations/RemoveNode.js +10 -3
- package/dist/transform/mutations/RemoveNode.js.flow +14 -3
- package/dist/transform/mutations/ReplaceNode.js +7 -2
- package/dist/transform/mutations/ReplaceNode.js.flow +5 -2
- package/dist/transform/mutations/ReplaceStatementWithMany.js.flow +5 -2
- package/dist/transform/mutations/utils/arrayUtils.js +14 -0
- package/dist/transform/mutations/utils/arrayUtils.js.flow +15 -0
- package/dist/transform/transform.js +38 -6
- package/dist/transform/transform.js.flow +40 -6
- package/dist/traverse/NodeEventGenerator.js.flow +1 -1
- package/dist/traverse/traverse.js +27 -3
- package/dist/traverse/traverse.js.flow +63 -10
- package/package.json +9 -4
- package/dist/transform/mutations/AddLeadingComments.js.flow +0 -49
- package/dist/transform/mutations/AddTrailingComments.js +0 -40
- 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
|
-
|
|
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
|
|
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
|
|
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 '
|
|
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 '
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
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 ${
|
|
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:
|
|
27
|
+
target: ReplaceStatementWithManyMutationNodes,
|
|
25
28
|
nodesToReplaceWith: $ReadOnlyArray<
|
|
26
|
-
DetachedNode<
|
|
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
|
-
}
|
|
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 {
|
|
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<
|
|
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
|
|
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 =
|
|
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 =
|
|
72
|
+
export function traverseWithContext<T = TraversalContextBase>(
|
|
73
|
+
code: string,
|
|
49
74
|
ast: Program,
|
|
50
75
|
scopeManager: ScopeManager,
|
|
51
|
-
additionalContext:
|
|
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:
|
|
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.
|
|
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
|
-
"
|
|
15
|
-
"hermes-
|
|
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
|
}
|