summaryrefslogtreecommitdiff
path: root/src/node_modules/javascript-obfuscator/src/ASTParserFacade.ts
blob: 671da7795d5e21c5cc6358928448eff16715e927 (plain)
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}`
        );
    }
}