hermes-transform 0.11.0 → 0.12.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.
@@ -11,9 +11,9 @@
11
11
  'use strict';
12
12
 
13
13
  import type {MaybeDetachedNode} from '../detachedNode';
14
- import type {Program, ESNode} from 'hermes-estree';
14
+ import type {Program} from 'hermes-estree';
15
15
 
16
- import {SimpleTraverser} from 'hermes-parser';
16
+ import {mutateESTreeASTForPrettier} from 'hermes-parser';
17
17
  import * as prettier from 'prettier';
18
18
  import {
19
19
  addCommentsToNode,
@@ -21,12 +21,12 @@ import {
21
21
  } from './comments/comments';
22
22
  import type {VisitorKeysType} from 'hermes-parser';
23
23
 
24
- export function print(
24
+ export async function print(
25
25
  ast: MaybeDetachedNode<Program>,
26
26
  originalCode: string,
27
27
  prettierOptions: {...} = {},
28
28
  visitorKeys?: ?VisitorKeysType,
29
- ): string {
29
+ ): Promise<string> {
30
30
  // $FlowExpectedError[incompatible-type] This is now safe to access.
31
31
  const program: Program = ast;
32
32
 
@@ -44,7 +44,7 @@ export function print(
44
44
  }
45
45
 
46
46
  // Fix up the AST to match what prettier expects.
47
- mutateASTForPrettier(program, visitorKeys);
47
+ mutateESTreeASTForPrettier(program, visitorKeys);
48
48
 
49
49
  // we need to delete the comments prop or else prettier will do
50
50
  // its own attachment pass after the mutation and duplicate the
@@ -52,125 +52,63 @@ export function print(
52
52
  // $FlowExpectedError[cannot-write]
53
53
  delete program.comments;
54
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
- );
55
+ switch (getPrettierMajorVersion()) {
56
+ case '3': {
57
+ // Lazy require this module as it only exists in prettier v3.
58
+ const prettierFlowPlugin = require('prettier/plugins/flow');
59
+ return prettier.format(
60
+ originalCode,
61
+ // $FlowExpectedError[incompatible-exact] - we don't want to create a dependency on the prettier types
62
+ {
63
+ ...prettierOptions,
64
+ parser: 'flow',
65
+ requirePragma: false,
66
+ plugins: [
67
+ {
68
+ parsers: {
69
+ flow: {
70
+ ...prettierFlowPlugin.parsers.flow,
71
+ parse() {
72
+ return program;
73
+ },
74
+ },
75
+ },
76
+ },
77
+ ],
78
+ },
79
+ );
80
+ }
81
+ case '2': {
82
+ return prettier.format(
83
+ originalCode,
84
+ // $FlowExpectedError[incompatible-exact] - we don't want to create a dependency on the prettier types
85
+ {
86
+ ...prettierOptions,
87
+ parser() {
88
+ return program;
89
+ },
90
+ },
91
+ );
92
+ }
93
+ case 'UNSUPPORTED':
94
+ default: {
95
+ throw new Error(
96
+ `Unknown or unsupported prettier version of "${prettier.version}". Only major versions 3 or 2 of prettier are supported.`,
97
+ );
98
+ }
99
+ }
65
100
  }
66
101
 
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);
102
+ function getPrettierMajorVersion(): '3' | '2' | 'UNSUPPORTED' {
103
+ const {version} = prettier;
93
104
 
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
- };
105
+ if (version.startsWith('3.')) {
106
+ return '3';
107
+ }
164
108
 
165
- case 'MemberExpression':
166
- // $FlowExpectedError[cannot-spread-interface]
167
- return {
168
- ...node,
169
- type: 'OptionalMemberExpression',
170
- object: transformChainExpression(node.object),
171
- };
172
- // No default
109
+ if (version.startsWith('2.')) {
110
+ return '2';
173
111
  }
174
112
 
175
- return node;
113
+ return 'UNSUPPORTED';
176
114
  }
@@ -20,8 +20,8 @@ var _parse = require("./parse");
20
20
 
21
21
  var _print = require("./print");
22
22
 
23
- function transform(originalCode, visitors, prettierOptions = {}) {
24
- const parseResult = (0, _parse.parse)(originalCode);
23
+ async function transform(originalCode, visitors, prettierOptions = {}) {
24
+ const parseResult = await (0, _parse.parse)(originalCode);
25
25
  const {
26
26
  ast,
27
27
  astWasMutated,
@@ -19,12 +19,12 @@ import {print} from './print';
19
19
 
20
20
  export type TransformVisitor = Visitor<TransformContextAdditions>;
21
21
 
22
- export function transform(
22
+ export async function transform(
23
23
  originalCode: string,
24
24
  visitors: TransformVisitor,
25
25
  prettierOptions: {...} = {},
26
- ): string {
27
- const parseResult = parse(originalCode);
26
+ ): Promise<string> {
27
+ const parseResult = await parse(originalCode);
28
28
 
29
29
  const {ast, astWasMutated, mutatedCode} = transformAST(parseResult, visitors);
30
30
  if (!astWasMutated) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermes-transform",
3
- "version": "0.11.0",
3
+ "version": "0.12.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",
@@ -12,12 +12,12 @@
12
12
  "@babel/code-frame": "^7.16.0",
13
13
  "esquery": "^1.4.0",
14
14
  "flow-enums-runtime": "^0.0.6",
15
- "hermes-eslint": "0.11.0",
16
- "hermes-estree": "0.11.0",
17
- "hermes-parser": "0.11.0"
15
+ "hermes-eslint": "0.12.0",
16
+ "hermes-estree": "0.12.0",
17
+ "hermes-parser": "0.12.0"
18
18
  },
19
19
  "peerDependencies": {
20
- "prettier": "^2.7.1"
20
+ "prettier": "^3.0.0 || ^2.7.1"
21
21
  },
22
22
  "files": [
23
23
  "dist",
@@ -1,349 +0,0 @@
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
- * @format
8
- */
9
-
10
- 'use strict';
11
-
12
- const stringWidth = require('string-width');
13
- const getLast = require('../utils/get-last.js');
14
-
15
- const notAsciiRegex = /[^\x20-\x7F]/;
16
-
17
- /**
18
- * @typedef {{backwards?: boolean}} SkipOptions
19
- */
20
-
21
- /**
22
- * @param {string | RegExp} chars
23
- * @returns {(text: string, index: number | false, opts?: SkipOptions) => number | false}
24
- */
25
- function skip(chars) {
26
- return (text, index, opts) => {
27
- const backwards = opts && opts.backwards;
28
-
29
- // Allow `skip` functions to be threaded together without having
30
- // to check for failures (did someone say monads?).
31
- /* istanbul ignore next */
32
- if (index === false) {
33
- return false;
34
- }
35
-
36
- const {length} = text;
37
- let cursor = index;
38
- while (cursor >= 0 && cursor < length) {
39
- const c = text.charAt(cursor);
40
- if (chars instanceof RegExp) {
41
- if (!chars.test(c)) {
42
- return cursor;
43
- }
44
- } else if (!chars.includes(c)) {
45
- return cursor;
46
- }
47
-
48
- backwards ? cursor-- : cursor++;
49
- }
50
-
51
- if (cursor === -1 || cursor === length) {
52
- // If we reached the beginning or end of the file, return the
53
- // out-of-bounds cursor. It's up to the caller to handle this
54
- // correctly. We don't want to indicate `false` though if it
55
- // actually skipped valid characters.
56
- return cursor;
57
- }
58
- return false;
59
- };
60
- }
61
-
62
- /**
63
- * @type {(text: string, index: number | false, opts?: SkipOptions) => number | false}
64
- */
65
- const skipWhitespace = skip(/\s/);
66
- /**
67
- * @type {(text: string, index: number | false, opts?: SkipOptions) => number | false}
68
- */
69
- const skipSpaces = skip(' \t');
70
- /**
71
- * @type {(text: string, index: number | false, opts?: SkipOptions) => number | false}
72
- */
73
- const skipToLineEnd = skip(',; \t');
74
- /**
75
- * @type {(text: string, index: number | false, opts?: SkipOptions) => number | false}
76
- */
77
- const skipEverythingButNewLine = skip(/[^\n\r]/);
78
-
79
- /**
80
- * @param {string} text
81
- * @param {number | false} index
82
- * @returns {number | false}
83
- */
84
- function skipInlineComment(text, index) {
85
- /* istanbul ignore next */
86
- if (index === false) {
87
- return false;
88
- }
89
-
90
- if (text.charAt(index) === '/' && text.charAt(index + 1) === '*') {
91
- for (let i = index + 2; i < text.length; ++i) {
92
- if (text.charAt(i) === '*' && text.charAt(i + 1) === '/') {
93
- return i + 2;
94
- }
95
- }
96
- }
97
- return index;
98
- }
99
-
100
- /**
101
- * @param {string} text
102
- * @param {number | false} index
103
- * @returns {number | false}
104
- */
105
- function skipTrailingComment(text, index) {
106
- /* istanbul ignore next */
107
- if (index === false) {
108
- return false;
109
- }
110
-
111
- if (text.charAt(index) === '/' && text.charAt(index + 1) === '/') {
112
- return skipEverythingButNewLine(text, index);
113
- }
114
- return index;
115
- }
116
-
117
- // This one doesn't use the above helper function because it wants to
118
- // test \r\n in order and `skip` doesn't support ordering and we only
119
- // want to skip one newline. It's simple to implement.
120
- /**
121
- * @param {string} text
122
- * @param {number | false} index
123
- * @param {SkipOptions=} opts
124
- * @returns {number | false}
125
- */
126
- function skipNewline(text, index, opts) {
127
- const backwards = opts && opts.backwards;
128
- if (index === false) {
129
- return false;
130
- }
131
-
132
- const atIndex = text.charAt(index);
133
- if (backwards) {
134
- // We already replace `\r\n` with `\n` before parsing
135
- /* istanbul ignore next */
136
- if (text.charAt(index - 1) === '\r' && atIndex === '\n') {
137
- return index - 2;
138
- }
139
- if (
140
- atIndex === '\n' ||
141
- atIndex === '\r' ||
142
- atIndex === '\u2028' ||
143
- atIndex === '\u2029'
144
- ) {
145
- return index - 1;
146
- }
147
- } else {
148
- // We already replace `\r\n` with `\n` before parsing
149
- /* istanbul ignore next */
150
- if (atIndex === '\r' && text.charAt(index + 1) === '\n') {
151
- return index + 2;
152
- }
153
- if (
154
- atIndex === '\n' ||
155
- atIndex === '\r' ||
156
- atIndex === '\u2028' ||
157
- atIndex === '\u2029'
158
- ) {
159
- return index + 1;
160
- }
161
- }
162
-
163
- return index;
164
- }
165
-
166
- /**
167
- * @param {string} text
168
- * @param {number} index
169
- * @param {SkipOptions=} opts
170
- * @returns {boolean}
171
- */
172
- function hasNewline(text, index, opts = {}) {
173
- const idx = skipSpaces(text, opts.backwards ? index - 1 : index, opts);
174
- const idx2 = skipNewline(text, idx, opts);
175
- return idx !== idx2;
176
- }
177
-
178
- /**
179
- * @param {string} text
180
- * @param {number} start
181
- * @param {number} end
182
- * @returns {boolean}
183
- */
184
- function hasNewlineInRange(text, start, end) {
185
- for (let i = start; i < end; ++i) {
186
- if (text.charAt(i) === '\n') {
187
- return true;
188
- }
189
- }
190
- return false;
191
- }
192
-
193
- /**
194
- * @param {string} text
195
- * @param {number} index
196
- * @returns {boolean}
197
- */
198
- function isNextLineEmptyAfterIndex(text, index) {
199
- /** @type {number | false} */
200
- let oldIdx = null;
201
- /** @type {number | false} */
202
- let idx = index;
203
- while (idx !== oldIdx) {
204
- // We need to skip all the potential trailing inline comments
205
- oldIdx = idx;
206
- idx = skipToLineEnd(text, idx);
207
- idx = skipInlineComment(text, idx);
208
- idx = skipSpaces(text, idx);
209
- }
210
- idx = skipTrailingComment(text, idx);
211
- idx = skipNewline(text, idx);
212
- return idx !== false && hasNewline(text, idx);
213
- }
214
-
215
- /**
216
- * @param {string} text
217
- * @param {number} idx
218
- * @returns {number | false}
219
- */
220
- function getNextNonSpaceNonCommentCharacterIndexWithStartIndex(text, idx) {
221
- /** @type {number | false} */
222
- let oldIdx = null;
223
- /** @type {number | false} */
224
- let nextIdx = idx;
225
- while (nextIdx !== oldIdx) {
226
- oldIdx = nextIdx;
227
- nextIdx = skipSpaces(text, nextIdx);
228
- nextIdx = skipInlineComment(text, nextIdx);
229
- nextIdx = skipTrailingComment(text, nextIdx);
230
- nextIdx = skipNewline(text, nextIdx);
231
- }
232
- return nextIdx;
233
- }
234
-
235
- /**
236
- * @template N
237
- * @param {string} text
238
- * @param {N} node
239
- * @param {(node: N) => number} locEnd
240
- * @returns {number | false}
241
- */
242
- function getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd) {
243
- return getNextNonSpaceNonCommentCharacterIndexWithStartIndex(
244
- text,
245
- locEnd(node),
246
- );
247
- }
248
-
249
- /**
250
- * @template N
251
- * @param {string} text
252
- * @param {N} node
253
- * @param {(node: N) => number} locEnd
254
- * @returns {string}
255
- */
256
- function getNextNonSpaceNonCommentCharacter(text, node, locEnd) {
257
- return text.charAt(
258
- // @ts-expect-error => TBD: can return false, should we define a fallback?
259
- getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd),
260
- );
261
- }
262
-
263
- /**
264
- * @param {string} text
265
- * @returns {number}
266
- */
267
- function getStringWidth(text) {
268
- if (!text) {
269
- return 0;
270
- }
271
-
272
- // shortcut to avoid needless string `RegExp`s, replacements, and allocations within `string-width`
273
- if (!notAsciiRegex.test(text)) {
274
- return text.length;
275
- }
276
-
277
- return stringWidth(text);
278
- }
279
-
280
- function addCommentHelper(node, comment) {
281
- const comments = node.comments || (node.comments = []);
282
- comments.push(comment);
283
- comment.printed = false;
284
- comment.nodeDescription = describeNodeForDebugging(node);
285
- }
286
-
287
- function addLeadingComment(node, comment) {
288
- comment.leading = true;
289
- comment.trailing = false;
290
- addCommentHelper(node, comment);
291
- }
292
-
293
- function addDanglingComment(node, comment, marker) {
294
- comment.leading = false;
295
- comment.trailing = false;
296
- if (marker) {
297
- comment.marker = marker;
298
- }
299
- addCommentHelper(node, comment);
300
- }
301
-
302
- function addTrailingComment(node, comment) {
303
- comment.leading = false;
304
- comment.trailing = true;
305
- addCommentHelper(node, comment);
306
- }
307
-
308
- /**
309
- * @param {any} object
310
- * @returns {object is Array<any>}
311
- */
312
- function isNonEmptyArray(object) {
313
- return Array.isArray(object) && object.length > 0;
314
- }
315
-
316
- function describeNodeForDebugging(node) {
317
- const nodeType = node.type || node.kind || '(unknown type)';
318
- let nodeName = String(
319
- node.name ||
320
- (node.id && (typeof node.id === 'object' ? node.id.name : node.id)) ||
321
- (node.key && (typeof node.key === 'object' ? node.key.name : node.key)) ||
322
- (node.value &&
323
- (typeof node.value === 'object' ? '' : String(node.value))) ||
324
- node.operator ||
325
- '',
326
- );
327
- if (nodeName.length > 20) {
328
- nodeName = nodeName.slice(0, 19) + '…';
329
- }
330
- return nodeType + (nodeName ? ' ' + nodeName : '');
331
- }
332
-
333
- module.exports = {
334
- getStringWidth,
335
- getLast,
336
- getNextNonSpaceNonCommentCharacterIndexWithStartIndex,
337
- getNextNonSpaceNonCommentCharacterIndex,
338
- getNextNonSpaceNonCommentCharacter,
339
- skipWhitespace,
340
- skipSpaces,
341
- skipNewline,
342
- isNextLineEmptyAfterIndex,
343
- hasNewline,
344
- hasNewlineInRange,
345
- addLeadingComment,
346
- addDanglingComment,
347
- addTrailingComment,
348
- isNonEmptyArray,
349
- };