Files
javascript-obfuscator-javas…/src/ASTParserFacade.ts
T

114 lines
3.5 KiB
TypeScript

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,
allowAwaitOutsideFunction: false,
allowReserved: true,
allowImportExportEverywhere: true,
allowReturnOutsideFunction: true,
allowSuperOutsideMethod: true,
onComment: comments,
sourceType
};
const program: acorn.Node & ESTree.Program = <acorn.Node & ESTree.Program>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 {
const isMissingLocationData = !position?.line || !position.column;
if (isMissingLocationData) {
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}`);
}
}