瀏覽代碼

Merge pull request #198 from javascript-obfuscator/esprima-facade

Esprima facade
Timofey Kachalov 7 年之前
父節點
當前提交
82ad565a06

+ 1 - 0
CHANGELOG.md

@@ -3,6 +3,7 @@ Change Log
 v0.14.3
 ---
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/195
+* Added code preview to `esprima` error messages.
 
 v0.14.2
 ---

文件差異過大導致無法顯示
+ 0 - 0
dist/index.js


+ 70 - 0
src/EsprimaFacade.ts

@@ -0,0 +1,70 @@
+import * as esprima from 'esprima';
+import * as ESTree from 'estree';
+
+import chalk, { Chalk } from 'chalk';
+
+/**
+ * Facade over `esprima` to handle parsing errors and provide more detailed error messages
+ */
+export class EsprimaFacade {
+    /**
+     * @type {Chalk}
+     */
+    private static readonly colorError: Chalk = chalk.red;
+
+    /**
+     * @type {number}
+     */
+    private static readonly nearestSymbolsCount: number = 10;
+
+    /**
+     * @param {string} input
+     * @param {ParseOptions} config
+     * @returns {Program}
+     */
+    public static parseScript (input: string, config: esprima.ParseOptions): ESTree.Program {
+        let lastMeta: esprima.NodeMeta | null = null;
+
+        try {
+            return esprima.parseScript(input, config, (node: ESTree.Node, meta: any) => lastMeta = meta);
+        } catch (error) {
+            return EsprimaFacade.processParsingError(input, error.message, lastMeta);
+        }
+    }
+
+    /**
+     * @param {string} sourceCode
+     * @param {string} errorMessage
+     * @param {"esprima".NodeMeta | null} meta
+     * @returns {never}
+     */
+    private static processParsingError (sourceCode: string, errorMessage: string, meta: esprima.NodeMeta | null): never {
+        if (!meta || !meta.start || !meta.end || !meta.start.column || !meta.end.column) {
+            throw new Error(errorMessage);
+        }
+
+        const lineNumberMatch: RegExpMatchArray | null = errorMessage.match(/Line *(\d*)/);
+
+        if (!lineNumberMatch) {
+            throw new Error(errorMessage);
+        }
+
+        const lineNumber: number = parseInt(lineNumberMatch[1], 10);
+        const sourceCodeLines: string[] = sourceCode.split(/\r?\n/);
+        const errorLine: string | undefined = sourceCodeLines[lineNumber - 1];
+
+        if (!errorLine) {
+            throw new Error(errorMessage);
+        }
+
+        const startErrorIndex: number = Math.max(0, meta.start.column - EsprimaFacade.nearestSymbolsCount);
+        const endErrorIndex: number = Math.min(errorLine.length, meta.end.column + EsprimaFacade.nearestSymbolsCount);
+
+        const formattedPointer: string = EsprimaFacade.colorError('>');
+        const formattedCodeSlice: string = `...${
+            errorLine.substring(startErrorIndex, endErrorIndex).replace(/^\s+/, '')
+        }...`;
+
+        throw new Error(`${errorMessage}\n${formattedPointer} ${formattedCodeSlice}`);
+    }
+}

+ 2 - 2
src/JavaScriptObfuscator.ts

@@ -1,7 +1,6 @@
 import { inject, injectable, } from 'inversify';
 import { ServiceIdentifiers } from './container/ServiceIdentifiers';
 
-import * as esprima from 'esprima';
 import * as escodegen from 'escodegen-wallaby';
 import * as ESTree from 'estree';
 import * as packageJson from 'pjson';
@@ -19,6 +18,7 @@ import { LoggingMessage } from './enums/logger/LoggingMessage';
 import { NodeTransformer } from './enums/node-transformers/NodeTransformer';
 import { TransformationStage } from './enums/node-transformers/TransformationStage';
 
+import { EsprimaFacade } from './EsprimaFacade';
 import { NodeGuards } from './node/NodeGuards';
 
 @injectable()
@@ -134,7 +134,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
      * @returns {Program}
      */
     private parseCode (sourceCode: string): ESTree.Program {
-        return esprima.parseScript(sourceCode, {
+        return EsprimaFacade.parseScript(sourceCode, {
             attachComment: true,
             loc: this.options.sourceMap
         });

+ 1 - 1
src/cli/utils/CLIUtils.ts

@@ -55,7 +55,7 @@ export class CLIUtils {
             try {
                 config = __non_webpack_require__(configPath);
             } catch (e) {
-                throw new ReferenceError('Given config path must be a valid file path');
+                throw new ReferenceError('Given config path must be a valid path of `.js` or `.json` file');
             }
         }
 

+ 11 - 0
src/declarations/esprima.d.ts

@@ -3,6 +3,17 @@
 import * as esprima from 'esprima';
 
 declare module 'esprima' {
+    export interface LineMeta {
+        line?: number;
+        column?: number;
+        offset?: number;
+    }
+
+    export interface NodeMeta {
+        start?: LineMeta;
+        end?: LineMeta;
+    }
+
     export interface ParseOptions {
         attachComment?: boolean;
     }

+ 6 - 0
test/declarations/index.d.ts

@@ -0,0 +1,6 @@
+/// <reference path="../../src/declarations/escodegen.d.ts" />
+/// <reference path="../../src/declarations/escodegen-wallaby.d.ts" />
+/// <reference path="../../src/declarations/esprima.d.ts" />
+/// <reference path="../../src/declarations/ESTree.d.ts" />
+/// <reference path="../../src/declarations/js-string-escape.d.ts" />
+/// <reference path="../../src/declarations/threads.d.ts" />

+ 1 - 0
test/index.spec.ts

@@ -17,6 +17,7 @@ import './unit-tests/cli/utils/SourceCodeReader.spec';
 import './unit-tests/decorators/initializable/Initializable.spec';
 import './unit-tests/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.spec';
 import './unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec';
+import './unit-tests/javascript-obfuscator/EsprimaFacade.spec';
 import './unit-tests/javascript-obfuscator/JavaScriptObfuscator.spec';
 import './unit-tests/logger/Logger.spec';
 import './unit-tests/node/node-appender/NodeAppender.spec';

+ 80 - 0
test/unit-tests/javascript-obfuscator/EsprimaFacade.spec.ts

@@ -0,0 +1,80 @@
+import { assert } from 'chai';
+import { EsprimaFacade } from '../../../src/EsprimaFacade';
+
+
+describe('EsprimaFacade', () => {
+    describe(`parseScript (input: string, config: esprima.ParseOptions): ESTree.Program`, () => {
+        describe(`\`Unexpected token\` error code preview`, () => {
+            describe('variant #1: 5 lines of code', () => {
+                const sourceCode: string = `` +
+                `var foo = 1;
+                var bar = 2;
+                var baz = 3;,
+                var bark = 4;
+                var hawk = 5;`;
+
+                let testFunc: () => void;
+
+                before(() => {
+                    testFunc = () => EsprimaFacade.parseScript(sourceCode, {});
+                });
+
+                it('should output code preview when `esprima` throws a parse error', () => {
+                    assert.throws(testFunc, /Line 3: Unexpected token ,\n.*\.\.\.var baz = 3;,\.\.\./);
+                });
+            });
+
+            describe('variant #2: 15 lines of code', () => {
+                const sourceCode: string = `` +
+                `var var1 = 1;
+                var var2 = 2;
+                var var3 = 3;
+                var var4 = 4;
+                var var5 = 5;
+                var var6 = 6;
+                var var7 = 7;
+                var var8 = 8;
+                var var9 = 9;
+                var var10 = 10;
+                var foo = 1;
+                var bar = 2;
+                var baz = 3;,
+                var bark = 4;
+                var hawk = 5;`;
+
+                let testFunc: () => void;
+
+                before(() => {
+                    testFunc = () => EsprimaFacade.parseScript(sourceCode, {});
+                });
+
+                it('should output code preview when `esprima` throws a parse error', () => {
+                    assert.throws(testFunc, /Line 13: Unexpected token ,\n.*\.\.\.var baz = 3;,\.\.\./);
+                });
+            });
+        });
+
+        describe(`\`Unexpected identifier\` error code preview`, () => {
+            const sourceCode: string = `` +
+                `function bar () {
+                    var a = 1;
+                }
+                functin baz () {
+                    var a = 1;
+                }
+                function bark () {
+                    var a = 1;
+                }`;
+
+            let testFunc: () => void;
+
+            before(() => {
+                testFunc = () => EsprimaFacade.parseScript(sourceCode, {});
+            });
+
+            it('should output code preview when `esprima` throws a parse error', () => {
+                assert.throws(testFunc, /Line 4: Unexpected identifier\n.*\.\.\.functin baz \(\) {\.\.\./);
+            });
+        });
+    });
+});

部分文件因文件數量過多而無法顯示