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
|
import * as acorn from 'acorn';
import * as ESTree from 'estree';
import chalk, { Chalk } from 'chalk';
/**
* Facade over AST parser `acorn`
*/
export class ASTParserFacade {
/**
* @type {Chalk}
*/
private static readonly colorError: Chalk = chalk.red;
/**
* @type {number}
*/
private static readonly nearestSymbolsCount: number = 15;
/**
* @type {acorn.Options['sourceType'][]}
*/
private static readonly sourceTypes: acorn.Options['sourceType'][] = [
'script',
'module'
];
/**
* @param {string} sourceCode
* @param {Options} config
* @returns {Program}
*/
public static parse (sourceCode: string, config: acorn.Options): ESTree.Program | never {
const sourceTypeLength: number = ASTParserFacade.sourceTypes.length;
for (let i: number = 0; i < sourceTypeLength; i++) {
try {
return ASTParserFacade.parseType(sourceCode, config, ASTParserFacade.sourceTypes[i]);
} catch (error) {
if (i < sourceTypeLength - 1) {
continue;
}
throw new Error(ASTParserFacade.processParsingError(
sourceCode,
error.message,
error.loc
));
}
}
throw new Error('Acorn parsing error');
}
/**
* @param {string} sourceCode
* @param {acorn.Options} inputConfig
* @param {acorn.Options["sourceType"]} sourceType
* @returns {Program}
*/
private static parseType (
sourceCode: string,
inputConfig: acorn.Options,
sourceType: acorn.Options['sourceType']
): ESTree.Program {
const comments: ESTree.Comment[] = [];
const config: acorn.Options = {
...inputConfig,
onComment: comments,
sourceType
};
const program: ESTree.Program = <any>acorn
.parse(sourceCode, config);
if (comments.length) {
program.comments = comments;
}
return program;
}
/**
* @param {string} sourceCode
* @param {string} errorMessage
* @param {Position | null} position
* @returns {never}
*/
private static processParsingError (
sourceCode: string,
errorMessage: string,
position: ESTree.Position | null
): never {
if (!position || !position.line || !position.column) {
throw new Error(errorMessage);
}
const sourceCodeLines: string[] = sourceCode.split(/\r?\n/);
const errorLine: string | undefined = sourceCodeLines[position.line - 1];
if (!errorLine) {
throw new Error(errorMessage);
}
const startErrorIndex: number = Math.max(0, position.column - ASTParserFacade.nearestSymbolsCount);
const endErrorIndex: number = Math.min(errorLine.length, position.column + ASTParserFacade.nearestSymbolsCount);
const formattedPointer: string = ASTParserFacade.colorError('>');
const formattedCodeSlice: string = `...${
errorLine.slice(startErrorIndex, endErrorIndex).replace(/^\s+/, '')
}...`;
throw new Error(
`ERROR at line ${position.line}: ${errorMessage}\n${formattedPointer} ${formattedCodeSlice}`
);
}
}
|