mirror of
https://github.com/javascript-obfuscator/javascript-obfuscator
synced 2026-08-09 12:42:29 +00:00
114 lines
3.5 KiB
TypeScript
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}`);
|
|
}
|
|
}
|