/
parse.ts
127 lines (118 loc) · 3.07 KB
/
parse.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ParserPlugin } from '@babel/parser';
import { codeFrameColumns } from '@babel/code-frame';
import * as parser from '../../src/parser';
function createError(
message: string,
line: number,
column: number,
): SyntaxError {
// Construct an error similar to the ones thrown by Babylon.
const error = new SyntaxError(`${message} (${line}:${column})`);
(error as any).loc = {
line,
column,
};
return error;
}
function parseWithBabelParser(text: string, jsx = true): any {
const babel = require('@babel/parser');
const plugins: ParserPlugin[] = [
'asyncGenerators',
'bigInt',
'classProperties',
'decorators-legacy',
'dynamicImport',
'estree',
'importMeta',
'logicalAssignment',
'nullishCoalescingOperator',
'numericSeparator',
'objectRestSpread',
'optionalChaining',
'typescript',
];
if (jsx) {
plugins.push('jsx');
}
return babel.parse(text, {
sourceType: 'unambiguous',
allowImportExportEverywhere: true,
allowReturnOutsideFunction: true,
ranges: true,
plugins,
});
}
function parseWithTypeScriptESTree(text: string, jsx = true): parser.AST<any> {
try {
const result = parser.parseAndGenerateServices(text, {
loc: true,
range: true,
tokens: false,
comment: false,
useJSXTextNode: true,
errorOnUnknownASTType: true,
/**
* Babel will always throw on these types of issues, so we enable
* them in typescript-estree when comparing behavior between the
* two parsers. By default, the TypeScript compiler is much more
* forgiving.
*/
errorOnTypeScriptSyntacticAndSemanticIssues: true,
jsx,
});
return result.ast;
} catch (e) {
throw createError(e.message, e.lineNumber, e.column);
}
}
interface ASTComparisonParseOptions {
parser: string;
jsx?: boolean;
}
export function parse(
text: string,
opts: ASTComparisonParseOptions,
): { parseError: any | null; ast: any | null } {
/**
* Always return a consistent interface, there will be times when we expect both
* parsers to fail to parse the invalid source.
*/
const result: { parseError: any | null; ast: any | null } = {
parseError: null,
ast: null,
};
try {
switch (opts.parser) {
case '@typescript-eslint/typescript-estree':
result.ast = parseWithTypeScriptESTree(text, opts.jsx);
break;
case '@babel/parser':
result.ast = parseWithBabelParser(text, opts.jsx);
break;
default:
throw new Error(
'Please provide a valid parser: either "typescript-estree" or "@babel/parser"',
);
}
} catch (error) {
const loc = error.loc;
if (loc) {
error.codeFrame = codeFrameColumns(
text,
{
start: {
line: loc.line,
column: loc.column + 1,
},
},
{
highlightCode: true,
},
);
error.message += `\n${error.codeFrame}`;
}
result.parseError = error;
}
return result;
}