hermes-parser 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/dist/HermesASTAdapter.js +0 -4
- package/dist/HermesASTAdapter.js.flow +0 -6
- package/dist/HermesParser.js.flow +1 -1
- package/dist/HermesParserWASM.js +1 -1
- package/dist/HermesToBabelAdapter.js +83 -4
- package/dist/HermesToBabelAdapter.js.flow +92 -3
- package/dist/HermesToESTreeAdapter.js +217 -1
- package/dist/HermesToESTreeAdapter.js.flow +218 -1
- package/dist/generated/visitor-keys.js +36 -28
- package/dist/getModuleDocblock.js +111 -0
- package/dist/getModuleDocblock.js.flow +116 -0
- package/package.json +8 -4
|
@@ -21,6 +21,7 @@ import type {HermesNode} from './HermesAST';
|
|
|
21
21
|
import type {ParserOptions} from './ParserOptions';
|
|
22
22
|
|
|
23
23
|
import HermesASTAdapter from './HermesASTAdapter';
|
|
24
|
+
import {getModuleDocblock} from './getModuleDocblock';
|
|
24
25
|
|
|
25
26
|
declare var BigInt: ?(value: $FlowFixMe) => mixed;
|
|
26
27
|
|
|
@@ -45,6 +46,9 @@ export default class HermesToESTreeAdapter extends HermesASTAdapter {
|
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
node.range = [loc.rangeStart, loc.rangeEnd];
|
|
49
|
+
|
|
50
|
+
delete node.start;
|
|
51
|
+
delete node.end;
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
mapNode(node: HermesNode): HermesNode {
|
|
@@ -81,9 +85,22 @@ export default class HermesToESTreeAdapter extends HermesASTAdapter {
|
|
|
81
85
|
return this.mapExportNamedDeclaration(node);
|
|
82
86
|
case 'ExportAllDeclaration':
|
|
83
87
|
return this.mapExportAllDeclaration(node);
|
|
88
|
+
case 'Property':
|
|
89
|
+
return this.mapProperty(node);
|
|
90
|
+
case 'FunctionDeclaration':
|
|
91
|
+
case 'FunctionExpression':
|
|
92
|
+
case 'ArrowFunctionExpression':
|
|
93
|
+
return this.mapFunction(node);
|
|
84
94
|
case 'PrivateName':
|
|
95
|
+
return this.mapPrivateName(node);
|
|
96
|
+
case 'ClassProperty':
|
|
85
97
|
case 'ClassPrivateProperty':
|
|
86
|
-
return this.
|
|
98
|
+
return this.mapClassProperty(node);
|
|
99
|
+
case 'MemberExpression':
|
|
100
|
+
case 'OptionalMemberExpression':
|
|
101
|
+
case 'CallExpression':
|
|
102
|
+
case 'OptionalCallExpression':
|
|
103
|
+
return this.mapChainExpression(node);
|
|
87
104
|
default:
|
|
88
105
|
return this.mapNodeDefault(node);
|
|
89
106
|
}
|
|
@@ -93,6 +110,8 @@ export default class HermesToESTreeAdapter extends HermesASTAdapter {
|
|
|
93
110
|
node = this.mapNodeDefault(node);
|
|
94
111
|
node.sourceType = this.getSourceType();
|
|
95
112
|
|
|
113
|
+
node.docblock = getModuleDocblock(node);
|
|
114
|
+
|
|
96
115
|
return node;
|
|
97
116
|
}
|
|
98
117
|
|
|
@@ -208,6 +227,17 @@ export default class HermesToESTreeAdapter extends HermesASTAdapter {
|
|
|
208
227
|
return this.mapNodeDefault(node);
|
|
209
228
|
}
|
|
210
229
|
|
|
230
|
+
mapProperty(nodeUnprocessed: HermesNode): HermesNode {
|
|
231
|
+
const node = this.mapNodeDefault(nodeUnprocessed);
|
|
232
|
+
|
|
233
|
+
if (node.value.type === 'FunctionExpression') {
|
|
234
|
+
node.value.loc.start = node.key.loc.end;
|
|
235
|
+
node.value.range[0] = node.key.range[1];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return node;
|
|
239
|
+
}
|
|
240
|
+
|
|
211
241
|
mapComment(node: HermesNode): HermesNode {
|
|
212
242
|
if (node.type === 'CommentBlock') {
|
|
213
243
|
node.type = 'Block';
|
|
@@ -217,4 +247,191 @@ export default class HermesToESTreeAdapter extends HermesASTAdapter {
|
|
|
217
247
|
|
|
218
248
|
return node;
|
|
219
249
|
}
|
|
250
|
+
|
|
251
|
+
mapFunction(nodeUnprocessed: HermesNode): HermesNode {
|
|
252
|
+
const node = this.mapNodeDefault(nodeUnprocessed);
|
|
253
|
+
|
|
254
|
+
switch (node.type) {
|
|
255
|
+
case 'FunctionDeclaration':
|
|
256
|
+
case 'FunctionExpression':
|
|
257
|
+
node.expression = false;
|
|
258
|
+
return node;
|
|
259
|
+
|
|
260
|
+
case 'ArrowFunctionExpression':
|
|
261
|
+
node.expression = node.body.type !== 'BlockStatement';
|
|
262
|
+
return node;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return node;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
mapChainExpression(nodeUnprocessed: HermesNode): HermesNode {
|
|
269
|
+
/*
|
|
270
|
+
NOTE - In the below comments `MemberExpression` and `CallExpression`
|
|
271
|
+
are completely interchangable. For terseness we just reference
|
|
272
|
+
one each time.
|
|
273
|
+
*/
|
|
274
|
+
|
|
275
|
+
/*
|
|
276
|
+
Hermes uses the old babel-style AST:
|
|
277
|
+
```
|
|
278
|
+
(one?.two).three?.four;
|
|
279
|
+
^^^^^^^^^^^^^^^^^^^^^^ OptionalMemberExpression
|
|
280
|
+
^^^^^^^^^^^^^^^^ MemberExpression
|
|
281
|
+
^^^^^^^^ OptionalMemberExpression
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
We need to convert it to the ESTree representation:
|
|
285
|
+
```
|
|
286
|
+
(one?.two).three?.four;
|
|
287
|
+
^^^^^^^^^^^^^^^^^^^^^^ ChainExpression
|
|
288
|
+
^^^^^^^^^^^^^^^^^^^^^^ MemberExpression[optional = true]
|
|
289
|
+
^^^^^^^^^^^^^^^^ MemberExpression[optional = false]
|
|
290
|
+
^^^^^^^^ ChainExpression
|
|
291
|
+
^^^^^^^^ MemberExpression[optional = true]
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
We do this by converting the AST and its children (depth first), and then unwrapping
|
|
295
|
+
the resulting AST as appropriate.
|
|
296
|
+
|
|
297
|
+
Put another way:
|
|
298
|
+
1) traverse to the leaf
|
|
299
|
+
2) if the current node is an `OptionalMemberExpression`:
|
|
300
|
+
a) if the `.object` is a `ChainExpression`:
|
|
301
|
+
i) unwrap the child (`node.object = child.expression`)
|
|
302
|
+
b) convert this node to a `MemberExpression[optional = true]`
|
|
303
|
+
c) wrap this node (`node = ChainExpression[expression = node]`)
|
|
304
|
+
3) if the current node is a `MembedExpression`:
|
|
305
|
+
a) convert this node to a `MemberExpression[optional = true]`
|
|
306
|
+
*/
|
|
307
|
+
|
|
308
|
+
const node = this.mapNodeDefault(nodeUnprocessed);
|
|
309
|
+
|
|
310
|
+
const {child, childKey, isOptional} = ((): {
|
|
311
|
+
child: HermesNode,
|
|
312
|
+
childKey: string,
|
|
313
|
+
isOptional: boolean,
|
|
314
|
+
} => {
|
|
315
|
+
const isOptional: boolean = node.optional === true;
|
|
316
|
+
if (node.type.endsWith('MemberExpression')) {
|
|
317
|
+
return {
|
|
318
|
+
child: node.object,
|
|
319
|
+
childKey: 'object',
|
|
320
|
+
isOptional,
|
|
321
|
+
};
|
|
322
|
+
} else if (node.type.endsWith('CallExpression')) {
|
|
323
|
+
return {
|
|
324
|
+
child: node.callee,
|
|
325
|
+
childKey: 'callee',
|
|
326
|
+
isOptional,
|
|
327
|
+
};
|
|
328
|
+
} else {
|
|
329
|
+
return {
|
|
330
|
+
child: node.expression,
|
|
331
|
+
childKey: 'expression',
|
|
332
|
+
isOptional: false,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
})();
|
|
336
|
+
|
|
337
|
+
const isChildUnwrappable =
|
|
338
|
+
child.type === 'ChainExpression' &&
|
|
339
|
+
// (x?.y).z is semantically different to `x?.y.z`.
|
|
340
|
+
// In the un-parenthesised case `.z` is only executed if and only if `x?.y` returns a non-nullish value.
|
|
341
|
+
// In the parenthesised case, `.z` is **always** executed, regardless of the return of `x?.y`.
|
|
342
|
+
// As such the AST is different between the two cases.
|
|
343
|
+
//
|
|
344
|
+
// In the hermes AST - any member part of a non-short-circuited optional chain is represented with `OptionalMemberExpression`
|
|
345
|
+
// so if we see a `MemberExpression`, then we know we've hit a parenthesis boundary.
|
|
346
|
+
node.type !== 'MemberExpression' &&
|
|
347
|
+
node.type !== 'CallExpression';
|
|
348
|
+
|
|
349
|
+
if (node.type.startsWith('Optional')) {
|
|
350
|
+
node.type = node.type.replace('Optional', '');
|
|
351
|
+
node.optional = isOptional;
|
|
352
|
+
} else {
|
|
353
|
+
node.optional = false;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!isChildUnwrappable && !isOptional) {
|
|
357
|
+
return node;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (isChildUnwrappable) {
|
|
361
|
+
const newChild = child.expression;
|
|
362
|
+
node[childKey] = newChild;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
type: 'ChainExpression',
|
|
367
|
+
expression: node,
|
|
368
|
+
loc: node.loc,
|
|
369
|
+
range: node.range,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
mapClassProperty(nodeUnprocessed: HermesNode): HermesNode {
|
|
374
|
+
const node = this.mapNodeDefault(nodeUnprocessed);
|
|
375
|
+
|
|
376
|
+
const key = (() => {
|
|
377
|
+
if (node.type === 'ClassPrivateProperty') {
|
|
378
|
+
const key = this.mapNodeDefault(node.key);
|
|
379
|
+
return {
|
|
380
|
+
type: 'PrivateIdentifier',
|
|
381
|
+
name: key.name,
|
|
382
|
+
range: key.range,
|
|
383
|
+
loc: key.loc,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return node.key;
|
|
388
|
+
})();
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
...node,
|
|
392
|
+
computed: node.type === 'ClassPrivateProperty' ? false : node.computed,
|
|
393
|
+
key,
|
|
394
|
+
type: 'PropertyDefinition',
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
mapPrivateName(node: HermesNode): HermesNode {
|
|
399
|
+
return {
|
|
400
|
+
type: 'PrivateIdentifier',
|
|
401
|
+
name: node.id.name,
|
|
402
|
+
// estree the location refers to the entire string including the hash token
|
|
403
|
+
range: node.range,
|
|
404
|
+
loc: node.loc,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
mapExportNamedDeclaration(nodeUnprocessed: HermesNode): HermesNode {
|
|
409
|
+
const node = super.mapExportNamedDeclaration(nodeUnprocessed);
|
|
410
|
+
|
|
411
|
+
const namespaceSpecifier = node.specifiers.find(
|
|
412
|
+
spec => spec.type === 'ExportNamespaceSpecifier',
|
|
413
|
+
);
|
|
414
|
+
if (namespaceSpecifier != null) {
|
|
415
|
+
if (node.specifiers.length !== 1) {
|
|
416
|
+
// this should already a hermes parser error - but let's be absolutely sure we're aligned with the spec
|
|
417
|
+
throw new Error('Cannot use an export all with any other specifiers');
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
type: 'ExportAllDeclaration',
|
|
421
|
+
source: node.source,
|
|
422
|
+
exportKind: node.exportKind ?? 'value',
|
|
423
|
+
exported: namespaceSpecifier.exported,
|
|
424
|
+
range: node.range,
|
|
425
|
+
loc: node.loc,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return node;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
mapExportAllDeclaration(nodeUnprocessed: HermesNode): HermesNode {
|
|
433
|
+
const node = super.mapExportAllDeclaration(nodeUnprocessed);
|
|
434
|
+
node.exported = node.exported ?? null;
|
|
435
|
+
return node;
|
|
436
|
+
}
|
|
220
437
|
}
|
|
@@ -77,6 +77,9 @@ const HERMES_AST_VISITOR_KEYS = {
|
|
|
77
77
|
param: 'Node',
|
|
78
78
|
body: 'Node'
|
|
79
79
|
},
|
|
80
|
+
ChainExpression: {
|
|
81
|
+
expression: 'Node'
|
|
82
|
+
},
|
|
80
83
|
ClassBody: {
|
|
81
84
|
body: 'NodeList'
|
|
82
85
|
},
|
|
@@ -102,18 +105,6 @@ const HERMES_AST_VISITOR_KEYS = {
|
|
|
102
105
|
id: 'Node',
|
|
103
106
|
typeParameters: 'Node'
|
|
104
107
|
},
|
|
105
|
-
ClassPrivateProperty: {
|
|
106
|
-
key: 'Node',
|
|
107
|
-
value: 'Node',
|
|
108
|
-
variance: 'Node',
|
|
109
|
-
typeAnnotation: 'Node'
|
|
110
|
-
},
|
|
111
|
-
ClassProperty: {
|
|
112
|
-
key: 'Node',
|
|
113
|
-
value: 'Node',
|
|
114
|
-
variance: 'Node',
|
|
115
|
-
typeAnnotation: 'Node'
|
|
116
|
-
},
|
|
117
108
|
ConditionalExpression: {
|
|
118
109
|
test: 'Node',
|
|
119
110
|
alternate: 'Node',
|
|
@@ -212,6 +203,7 @@ const HERMES_AST_VISITOR_KEYS = {
|
|
|
212
203
|
},
|
|
213
204
|
ExistsTypeAnnotation: {},
|
|
214
205
|
ExportAllDeclaration: {
|
|
206
|
+
exported: 'Node',
|
|
215
207
|
source: 'Node'
|
|
216
208
|
},
|
|
217
209
|
ExportDefaultDeclaration: {
|
|
@@ -222,9 +214,6 @@ const HERMES_AST_VISITOR_KEYS = {
|
|
|
222
214
|
specifiers: 'NodeList',
|
|
223
215
|
source: 'Node'
|
|
224
216
|
},
|
|
225
|
-
ExportNamespaceSpecifier: {
|
|
226
|
-
exported: 'Node'
|
|
227
|
-
},
|
|
228
217
|
ExportSpecifier: {
|
|
229
218
|
exported: 'Node',
|
|
230
219
|
local: 'Node'
|
|
@@ -449,22 +438,11 @@ const HERMES_AST_VISITOR_KEYS = {
|
|
|
449
438
|
impltype: 'Node',
|
|
450
439
|
supertype: 'Node'
|
|
451
440
|
},
|
|
452
|
-
OptionalCallExpression: {
|
|
453
|
-
callee: 'Node',
|
|
454
|
-
typeArguments: 'Node',
|
|
455
|
-
arguments: 'NodeList'
|
|
456
|
-
},
|
|
457
441
|
OptionalIndexedAccessType: {
|
|
458
442
|
objectType: 'Node',
|
|
459
443
|
indexType: 'Node'
|
|
460
444
|
},
|
|
461
|
-
|
|
462
|
-
object: 'Node',
|
|
463
|
-
property: 'Node'
|
|
464
|
-
},
|
|
465
|
-
PrivateName: {
|
|
466
|
-
id: 'Node'
|
|
467
|
-
},
|
|
445
|
+
PrivateIdentifier: {},
|
|
468
446
|
Program: {
|
|
469
447
|
body: 'NodeList'
|
|
470
448
|
},
|
|
@@ -472,6 +450,12 @@ const HERMES_AST_VISITOR_KEYS = {
|
|
|
472
450
|
key: 'Node',
|
|
473
451
|
value: 'Node'
|
|
474
452
|
},
|
|
453
|
+
PropertyDefinition: {
|
|
454
|
+
key: 'Node',
|
|
455
|
+
value: 'Node',
|
|
456
|
+
variance: 'Node',
|
|
457
|
+
typeAnnotation: 'Node'
|
|
458
|
+
},
|
|
475
459
|
QualifiedTypeIdentifier: {
|
|
476
460
|
qualification: 'Node',
|
|
477
461
|
id: 'Node'
|
|
@@ -600,6 +584,30 @@ const HERMES_AST_VISITOR_KEYS = {
|
|
|
600
584
|
returnType: 'Node',
|
|
601
585
|
typeParameters: 'NodeList'
|
|
602
586
|
},
|
|
603
|
-
Import: {}
|
|
587
|
+
Import: {},
|
|
588
|
+
ClassProperty: {
|
|
589
|
+
key: 'Node',
|
|
590
|
+
value: 'Node',
|
|
591
|
+
variance: 'Node',
|
|
592
|
+
typeAnnotation: 'Node'
|
|
593
|
+
},
|
|
594
|
+
ClassPrivateProperty: {
|
|
595
|
+
key: 'Node',
|
|
596
|
+
value: 'Node',
|
|
597
|
+
variance: 'Node',
|
|
598
|
+
typeAnnotation: 'Node'
|
|
599
|
+
},
|
|
600
|
+
PrivateName: {
|
|
601
|
+
id: 'Node'
|
|
602
|
+
},
|
|
603
|
+
OptionalCallExpression: {
|
|
604
|
+
callee: 'Node',
|
|
605
|
+
typeArguments: 'Node',
|
|
606
|
+
arguments: 'NodeList'
|
|
607
|
+
},
|
|
608
|
+
OptionalMemberExpression: {
|
|
609
|
+
object: 'Node',
|
|
610
|
+
property: 'Node'
|
|
611
|
+
}
|
|
604
612
|
};
|
|
605
613
|
exports.HERMES_AST_VISITOR_KEYS = HERMES_AST_VISITOR_KEYS;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
*/
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
Object.defineProperty(exports, "__esModule", {
|
|
13
|
+
value: true
|
|
14
|
+
});
|
|
15
|
+
exports.getModuleDocblock = getModuleDocblock;
|
|
16
|
+
exports.parseDocblockString = parseDocblockString;
|
|
17
|
+
const DIRECTIVE_REGEX = /^\s*@([a-zA-Z0-9_-]+)( +.+)?$/;
|
|
18
|
+
|
|
19
|
+
function parseDocblockString(docblock) {
|
|
20
|
+
const directiveLines = docblock.split('\n') // remove the leading " *" from each line
|
|
21
|
+
.map(line => line.trimStart().replace(/^\* ?/, '').trim()).filter(line => line.startsWith('@'));
|
|
22
|
+
const directives = {};
|
|
23
|
+
|
|
24
|
+
for (const line of directiveLines) {
|
|
25
|
+
var _match$;
|
|
26
|
+
|
|
27
|
+
const match = DIRECTIVE_REGEX.exec(line);
|
|
28
|
+
|
|
29
|
+
if (match == null) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const name = match[1]; // explicitly use an empty string if there's no value
|
|
34
|
+
// this way the array length tracks how many instances of the directive there was
|
|
35
|
+
|
|
36
|
+
const value = ((_match$ = match[2]) != null ? _match$ : '').trim();
|
|
37
|
+
|
|
38
|
+
if (directives[name]) {
|
|
39
|
+
directives[name].push(value);
|
|
40
|
+
} else {
|
|
41
|
+
directives[name] = [value];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return directives;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getModuleDocblock(hermesProgram) {
|
|
49
|
+
const docblockNode = (() => {
|
|
50
|
+
if (hermesProgram.type !== 'Program') {
|
|
51
|
+
return null;
|
|
52
|
+
} // $FlowExpectedError[incompatible-type] - escape out of the unsafe hermes types
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
const program = hermesProgram;
|
|
56
|
+
|
|
57
|
+
if (program.comments.length === 0) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const firstComment = (() => {
|
|
62
|
+
const first = program.comments[0];
|
|
63
|
+
|
|
64
|
+
if (first.type === 'Block') {
|
|
65
|
+
return first;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (program.comments.length === 1) {
|
|
69
|
+
return null;
|
|
70
|
+
} // ESLint will always strip out the shebang comment from the code before passing it to the parser
|
|
71
|
+
// https://github.com/eslint/eslint/blob/21d647904dc30f9484b22acdd9243a6d0ecfba38/lib/linter/linter.js#L779
|
|
72
|
+
// this means that we're forced to parse it as a line comment :(
|
|
73
|
+
// this hacks around it by selecting the second comment in this case
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
const second = program.comments[1];
|
|
77
|
+
|
|
78
|
+
if (first.type === 'Line' && first.range[0] === 0 && second.type === 'Block') {
|
|
79
|
+
return second;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
})();
|
|
84
|
+
|
|
85
|
+
if (firstComment == null) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
/*
|
|
89
|
+
Handle cases like this where the comment isn't actually the first thing in the code:
|
|
90
|
+
```
|
|
91
|
+
const x = 1; /* docblock *./
|
|
92
|
+
```
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if (program.body.length > 0 && program.body[0].range[0] < firstComment.range[0]) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return firstComment;
|
|
101
|
+
})();
|
|
102
|
+
|
|
103
|
+
if (docblockNode == null) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
directives: parseDocblockString(docblockNode.value),
|
|
109
|
+
comment: docblockNode
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
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
|
|
8
|
+
* @format
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
import type {HermesNode} from './HermesAST';
|
|
14
|
+
import type {DocblockDirectives, Program} from 'hermes-estree';
|
|
15
|
+
|
|
16
|
+
const DIRECTIVE_REGEX = /^\s*@([a-zA-Z0-9_-]+)( +.+)?$/;
|
|
17
|
+
|
|
18
|
+
export function parseDocblockString(docblock: string): DocblockDirectives {
|
|
19
|
+
const directiveLines = docblock
|
|
20
|
+
.split('\n')
|
|
21
|
+
// remove the leading " *" from each line
|
|
22
|
+
.map(line => line.trimStart().replace(/^\* ?/, '').trim())
|
|
23
|
+
.filter(line => line.startsWith('@'));
|
|
24
|
+
|
|
25
|
+
const directives: {
|
|
26
|
+
[string]: Array<string>,
|
|
27
|
+
} = {};
|
|
28
|
+
|
|
29
|
+
for (const line of directiveLines) {
|
|
30
|
+
const match = DIRECTIVE_REGEX.exec(line);
|
|
31
|
+
if (match == null) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const name = match[1];
|
|
35
|
+
// explicitly use an empty string if there's no value
|
|
36
|
+
// this way the array length tracks how many instances of the directive there was
|
|
37
|
+
const value = (match[2] ?? '').trim();
|
|
38
|
+
if (directives[name]) {
|
|
39
|
+
directives[name].push(value);
|
|
40
|
+
} else {
|
|
41
|
+
directives[name] = [value];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return directives;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getModuleDocblock(
|
|
49
|
+
hermesProgram: HermesNode,
|
|
50
|
+
): Program['docblock'] {
|
|
51
|
+
const docblockNode = (() => {
|
|
52
|
+
if (hermesProgram.type !== 'Program') {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// $FlowExpectedError[incompatible-type] - escape out of the unsafe hermes types
|
|
57
|
+
const program: Program = hermesProgram;
|
|
58
|
+
|
|
59
|
+
if (program.comments.length === 0) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const firstComment = (() => {
|
|
64
|
+
const first = program.comments[0];
|
|
65
|
+
if (first.type === 'Block') {
|
|
66
|
+
return first;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (program.comments.length === 1) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ESLint will always strip out the shebang comment from the code before passing it to the parser
|
|
74
|
+
// https://github.com/eslint/eslint/blob/21d647904dc30f9484b22acdd9243a6d0ecfba38/lib/linter/linter.js#L779
|
|
75
|
+
// this means that we're forced to parse it as a line comment :(
|
|
76
|
+
// this hacks around it by selecting the second comment in this case
|
|
77
|
+
const second = program.comments[1];
|
|
78
|
+
if (
|
|
79
|
+
first.type === 'Line' &&
|
|
80
|
+
first.range[0] === 0 &&
|
|
81
|
+
second.type === 'Block'
|
|
82
|
+
) {
|
|
83
|
+
return second;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
})();
|
|
88
|
+
if (firstComment == null) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/*
|
|
93
|
+
Handle cases like this where the comment isn't actually the first thing in the code:
|
|
94
|
+
```
|
|
95
|
+
const x = 1; /* docblock *./
|
|
96
|
+
```
|
|
97
|
+
*/
|
|
98
|
+
if (
|
|
99
|
+
program.body.length > 0 &&
|
|
100
|
+
program.body[0].range[0] < firstComment.range[0]
|
|
101
|
+
) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return firstComment;
|
|
106
|
+
})();
|
|
107
|
+
|
|
108
|
+
if (docblockNode == null) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
directives: parseDocblockString(docblockNode.value),
|
|
114
|
+
comment: docblockNode,
|
|
115
|
+
};
|
|
116
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermes-parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "A JavaScript parser built from the Hermes engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,12 +9,16 @@
|
|
|
9
9
|
"url": "git@github.com:facebook/hermes.git"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"hermes-estree": "0.
|
|
12
|
+
"hermes-estree": "0.9.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"
|
|
15
|
+
"@babel/parser": "7.7.4",
|
|
16
|
+
"espree": "9.3.2",
|
|
17
|
+
"hermes-transform": "0.9.0"
|
|
16
18
|
},
|
|
17
19
|
"files": [
|
|
18
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"LICENCE",
|
|
22
|
+
"README.md"
|
|
19
23
|
]
|
|
20
24
|
}
|