diff --git a/README.md b/README.md index 91edbb2..85e7f61 100644 --- a/README.md +++ b/README.md @@ -68,22 +68,19 @@ require(Buffer.from("6673", "hex").toString()); Then use `js-x-ray` to run an analysis of the JavaScript code: ```js import { runASTAnalysis } from "@nodesecure/js-x-ray"; -import { readFileSync } from "fs"; +import { readFileSync } from "node:fs"; -const str = readFileSync("./file.js", "utf-8"); -const { warnings, dependencies } = runASTAnalysis(str); +const { warnings, dependencies } = runASTAnalysis( + readFileSync("./file.js", "utf-8") +); -const dependenciesName = [...dependencies]; -const inTryDeps = [...dependencies.getDependenciesInTryStatement()]; - -console.log(dependenciesName); -console.log(inTryDeps); +console.log(dependencies); console.dir(warnings, { depth: null }); ``` The analysis will return: `http` (in try), `crypto`, `util` and `fs`. -> [!NOTE] +> [!TIP] > There is also a lot of suspicious code example in the `./examples` cases directory. Feel free to try the tool on these files. ## Warnings diff --git a/index.d.ts b/index.d.ts index 502906f..7714596 100644 --- a/index.d.ts +++ b/index.d.ts @@ -4,7 +4,9 @@ import { Report, ReportOnFile, RuntimeFileOptions, - RuntimeOptions + RuntimeOptions, + SourceLocation, + Dependency } from "./types/api.js"; import { Warning, @@ -13,7 +15,6 @@ import { WarningName, WarningNameWithValue } from "./types/warnings.js"; -import { ASTDeps, Dependency } from "./types/astdeps.js"; declare const warnings: Record>; @@ -25,7 +26,7 @@ export { ReportOnFile, RuntimeFileOptions, RuntimeOptions, - ASTDeps, + SourceLocation, Dependency, Warning, WarningDefault, diff --git a/index.js b/index.js index 6682834..fdcff90 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,7 @@ import * as meriyah from "meriyah"; import isMinified from "is-minified-code"; // Import Internal Dependencies -import Analysis from "./src/Analysis.js"; +import { SourceFile } from "./src/SourceFile.js"; import { warnings } from "./src/warnings.js"; import * as utils from "./src/utils.js"; @@ -20,7 +20,10 @@ const kMeriyahDefaultOptions = { jsx: true }; -export function runASTAnalysis(str, options = Object.create(null)) { +export function runASTAnalysis( + str, + options = Object.create(null) +) { const { module = true, isMinified = false, @@ -35,8 +38,7 @@ export function runASTAnalysis(str, options = Object.create(null)) { removeHTMLComments }); - const sastAnalysis = new Analysis(); - sastAnalysis.analyzeSourceString(str); + const source = new SourceFile(str); // we walk each AST Nodes, this is a purely synchronous I/O walk(body, { @@ -46,23 +48,24 @@ export function runASTAnalysis(str, options = Object.create(null)) { return; } - const action = sastAnalysis.walk(node); + const action = source.walk(node); if (action === "skip") { this.skip(); } } }); - const dependencies = sastAnalysis.dependencies; - const { idsLengthAvg, stringScore, warnings } = sastAnalysis.getResult(isMinified); - const isOneLineRequire = body.length <= 1 && dependencies.size <= 1; - return { - dependencies, warnings, idsLengthAvg, stringScore, isOneLineRequire + ...source.getResult(isMinified), + dependencies: source.dependencies, + isOneLineRequire: body.length <= 1 && source.dependencies.size <= 1 }; } -export async function runASTAnalysisOnFile(pathToFile, options = {}) { +export async function runASTAnalysisOnFile( + pathToFile, + options = {} +) { try { const { packageName = null, @@ -80,7 +83,7 @@ export async function runASTAnalysisOnFile(pathToFile, options = {}) { removeHTMLComments }); if (packageName !== null) { - data.dependencies.removeByName(packageName); + data.dependencies.delete(packageName); } return { diff --git a/src/ASTDeps.js b/src/ASTDeps.js deleted file mode 100644 index fe204fb..0000000 --- a/src/ASTDeps.js +++ /dev/null @@ -1,63 +0,0 @@ - -export default class ASTDeps { - #inTry = false; - dependencies = Object.create(null); - - get isInTryStmt() { - return this.#inTry; - } - - set isInTryStmt(value) { - if (typeof value !== "boolean") { - throw new TypeError("value must be a boolean!"); - } - - this.#inTry = value; - } - - removeByName(name) { - if (Reflect.has(this.dependencies, name)) { - delete this.dependencies[name]; - } - } - - add(depName, location = null, unsafe = false) { - if (typeof depName !== "string" || depName.trim() === "") { - return; - } - - const cleanDepName = depName.charAt(depName.length - 1) === "/" ? depName.slice(0, -1) : depName; - const dep = { - unsafe, - inTry: this.isInTryStmt - }; - if (location !== null) { - dep.location = location; - } - this.dependencies[cleanDepName] = dep; - } - - has(depName) { - if (depName.trim() === "") { - return false; - } - - return Reflect.has(this.dependencies, depName); - } - - get size() { - return Object.keys(this.dependencies).length; - } - - * getDependenciesInTryStatement() { - for (const [depName, props] of Object.entries(this.dependencies)) { - if (props.inTry === true && props.unsafe === false) { - yield depName; - } - } - } - - * [Symbol.iterator]() { - yield* Object.keys(this.dependencies); - } -} diff --git a/src/Analysis.js b/src/SourceFile.js similarity index 87% rename from src/Analysis.js rename to src/SourceFile.js index 18f99bc..30150d5 100644 --- a/src/Analysis.js +++ b/src/SourceFile.js @@ -5,7 +5,6 @@ import { VariableTracer } from "@nodesecure/estree-ast-utils"; // Import Internal Dependencies import { rootLocation, toArrayLocation } from "./utils.js"; import { generateWarning } from "./warnings.js"; -import ASTDeps from "./ASTDeps.js"; import { isObfuscatedCode, hasTrojanSource } from "./obfuscators/index.js"; import { runOnProbes } from "./probes/index.js"; @@ -18,7 +17,8 @@ const kDictionaryStrParts = [ const kMaximumEncodedLiterals = 10; -export default class Analysis { +export class SourceFile { + inTryStatement = false; hasDictionaryString = false; hasPrefixedIdentifiers = false; varkinds = { var: 0, let: 0, const: 0 }; @@ -34,17 +34,35 @@ export default class Analysis { }; identifiersName = []; - constructor() { + constructor(sourceCodeString) { this.tracer = new VariableTracer() .enableDefaultTracing() .trace("crypto.createHash", { followConsecutiveAssignment: true, moduleName: "crypto" }); - this.dependencies = new ASTDeps(); + this.dependencies = new Map(); this.encodedLiterals = new Map(); this.warnings = []; this.literalScores = []; + + if (hasTrojanSource(sourceCodeString)) { + this.addWarning("obfuscated-code", "trojan-source"); + } + } + + addDependency(name, location = null, unsafe = false) { + if (typeof name !== "string" || name.trim() === "") { + return; + } + + const dependencyName = name.charAt(name.length - 1) === "/" ? + name.slice(0, -1) : name; + this.dependencies.set(dependencyName, { + unsafe, + inTry: this.inTryStatement, + ...(location === null ? {} : { location }) + }); } addWarning(name, value, location = rootLocation()) { @@ -68,12 +86,6 @@ export default class Analysis { } } - analyzeSourceString(sourceString) { - if (hasTrojanSource(sourceString)) { - this.addWarning("obfuscated-code", "trojan-source"); - } - } - analyzeString(str) { const score = Utils.stringSuspicionScore(str); if (score !== 0) { @@ -143,10 +155,10 @@ export default class Analysis { // Detect TryStatement and CatchClause to known which dependency is required in a Try {} clause if (node.type === "TryStatement" && typeof node.handler !== "undefined") { - this.dependencies.isInTryStmt = true; + this.inTryStatement = true; } else if (node.type === "CatchClause") { - this.dependencies.isInTryStmt = false; + this.inTryStatement = false; } return runOnProbes(node, this); diff --git a/src/probes/isImportDeclaration.js b/src/probes/isImportDeclaration.js index d58aa49..4cffb6d 100644 --- a/src/probes/isImportDeclaration.js +++ b/src/probes/isImportDeclaration.js @@ -21,7 +21,7 @@ function main(node, options) { if (node.source.value.startsWith("data:text/javascript")) { analysis.addWarning("unsafe-import", node.source.value, node.loc); } - analysis.dependencies.add(node.source.value, node.loc); + analysis.addDependency(node.source.value, node.loc); } export default { diff --git a/src/probes/isLiteral.js b/src/probes/isLiteral.js index 9a8388d..0f437d1 100644 --- a/src/probes/isLiteral.js +++ b/src/probes/isLiteral.js @@ -34,7 +34,7 @@ function main(node, options) { // If the value we are retrieving is the name of a Node.js dependency, // then we add it to the dependencies list and we throw an unsafe-import at the current location. if (kNodeDeps.has(value)) { - analysis.dependencies.add(value, node.loc); + analysis.addDependency(value, node.loc); analysis.addWarning("unsafe-import", null, node.loc); } else if (value === "require" || !Hex.isSafe(node.value)) { diff --git a/src/probes/isRequire.js b/src/probes/isRequire.js index eb11774..03c08d8 100644 --- a/src/probes/isRequire.js +++ b/src/probes/isRequire.js @@ -38,7 +38,7 @@ function main(node, options) { // const foo = "http"; require(foo); case "Identifier": if (analysis.tracer.literalIdentifiers.has(arg.name)) { - analysis.dependencies.add( + analysis.addDependency( analysis.tracer.literalIdentifiers.get(arg.name), node.loc ); @@ -50,7 +50,7 @@ function main(node, options) { // require("http") case "Literal": - analysis.dependencies.add(arg.value, node.loc); + analysis.addDependency(arg.value, node.loc); break; // require(["ht", "tp"]) @@ -63,7 +63,7 @@ function main(node, options) { analysis.addWarning("unsafe-import", null, node.loc); } else { - analysis.dependencies.add(value, node.loc); + analysis.addDependency(value, node.loc); } break; } @@ -80,7 +80,7 @@ function main(node, options) { tracer, stopOnUnsupportedNode: true }); - analysis.dependencies.add([...iter].join(""), node.loc); + analysis.addDependency([...iter].join(""), node.loc); } catch { analysis.addWarning("unsafe-import", null, node.loc); @@ -91,7 +91,7 @@ function main(node, options) { // require(Buffer.from("...", "hex").toString()); case "CallExpression": { walkRequireCallExpression(arg, tracer) - .forEach((depName) => analysis.dependencies.add(depName, node.loc, true)); + .forEach((depName) => analysis.addDependency(depName, node.loc, true)); analysis.addWarning("unsafe-import", null, node.loc); diff --git a/test/astdeps.spec.js b/test/astdeps.spec.js deleted file mode 100644 index 60978bb..0000000 --- a/test/astdeps.spec.js +++ /dev/null @@ -1,82 +0,0 @@ -// Import Node.js Dependencies -import { describe, it } from "node:test"; -import assert from "node:assert"; - -// Import Internal Dependencies -import ASTDeps from "../src/ASTDeps.js"; - -describe("ASTDeps", () => { - it("assert ASTDeps class default properties and values", () => { - const deps = new ASTDeps(); - - assert.strictEqual(deps.isInTryStmt, false); - assert.ok(typeof deps.dependencies === "object"); - assert.strictEqual( - Object.getPrototypeOf(deps.dependencies), - null - ); - assert.strictEqual(deps.size, 0); - assert.deepEqual([...deps], []); - }); - - it("add values to ASTDeps instance", () => { - const deps = new ASTDeps(); - deps.add("foo"); - deps.isInTryStmt = true; - deps.add("boo"); - - assert.strictEqual(deps.size, 2); - assert.deepEqual([...deps], ["foo", "boo"]); - assert.deepEqual([...deps.getDependenciesInTryStatement()], ["boo"]); - }); - - it("add method should cleanup ending slash", () => { - const deps = new ASTDeps(); - deps.add("foo/"); - - assert.strictEqual(deps.size, 1); - assert.ok(deps.has("foo")); - }); - - it("delete values from ASTDeps instance", () => { - const deps = new ASTDeps(); - deps.add("foo"); - deps.removeByName("foo"); - deps.removeByName("boo"); - - assert.strictEqual(deps.size, 0); - }); - - it("isIntryStmt must be a boolean!", () => { - const deps = new ASTDeps(); - - assert.throws(() => { - deps.isInTryStmt = 1; - }, new TypeError("value must be a boolean!")); - }); - - it("should assert presence of a dependency", () => { - const deps = new ASTDeps(); - - deps.add("foo"); - - assert.strictEqual(deps.has("foo"), true); - assert.strictEqual(deps.has("bar"), false); - assert.strictEqual(deps.has(""), false); - }); - - it("should not add dependency if not a string primitive", () => { - const deps = new ASTDeps(); - - deps.add(5); - assert.strictEqual(deps.size, 0); - }); - - it("should not add dependency if provided with empty string", () => { - const deps = new ASTDeps(); - - deps.add(""); - assert.strictEqual(deps.size, 0); - }); -}); - diff --git a/test/module.spec.js b/test/module.spec.js index 10cd85a..c8cde57 100644 --- a/test/module.spec.js +++ b/test/module.spec.js @@ -26,7 +26,7 @@ test("it should be capable to extract dependencies name for ECMAScript Modules ( assert.strictEqual(warnings.length, 0); assert.deepEqual( - [...dependencies].sort(), + [...dependencies.keys()].sort(), ["http", "fs", "xd"].sort() ); }); diff --git a/test/probes/isImportDeclaration.spec.js b/test/probes/isImportDeclaration.spec.js index 42901e4..b7e88ba 100644 --- a/test/probes/isImportDeclaration.spec.js +++ b/test/probes/isImportDeclaration.spec.js @@ -12,8 +12,8 @@ test("should detect 1 dependency for an ImportNamespaceSpecifier", () => { const { analysis } = getSastAnalysis(str, isImportDeclaration) .execute(ast.body); - const { dependencies } = analysis.dependencies; - assert.ok("bar" in dependencies); + const { dependencies } = analysis; + assert.ok(dependencies.has("bar")); }); test("should detect 1 dependency for an ImportDefaultSpecifier", () => { @@ -22,8 +22,8 @@ test("should detect 1 dependency for an ImportDefaultSpecifier", () => { const { analysis } = getSastAnalysis(str, isImportDeclaration) .execute(ast.body); - const { dependencies } = analysis.dependencies; - assert.ok("bar" in dependencies); + const { dependencies } = analysis; + assert.ok(dependencies.has("bar")); }); test("should detect 1 dependency for an ImportSpecifier", () => { @@ -32,8 +32,8 @@ test("should detect 1 dependency for an ImportSpecifier", () => { const { analysis } = getSastAnalysis(str, isImportDeclaration) .execute(ast.body); - const { dependencies } = analysis.dependencies; - assert.ok("bar" in dependencies); + const { dependencies } = analysis; + assert.ok(dependencies.has("bar")); }); test("should detect 1 dependency with no specificiers", () => { @@ -42,8 +42,8 @@ test("should detect 1 dependency with no specificiers", () => { const { analysis } = getSastAnalysis(str, isImportDeclaration) .execute(ast.body); - const { dependencies } = analysis.dependencies; - assert.ok("bar" in dependencies); + const { dependencies } = analysis; + assert.ok(dependencies.has("bar")); }); test("should detect an unsafe import using data:text/javascript and throw a unsafe-import warning", () => { diff --git a/test/probes/isLiteral.spec.js b/test/probes/isLiteral.spec.js index 85dcff9..471e559 100644 --- a/test/probes/isLiteral.spec.js +++ b/test/probes/isLiteral.spec.js @@ -19,7 +19,7 @@ test("should throw an unsafe-import because the hexadecimal string is equal to t const warning = sastAnalysis.getWarning("unsafe-import"); assert.strictEqual(warning.kind, "unsafe-import"); - assert.ok("http" in sastAnalysis.dependencies()); + assert.ok(sastAnalysis.dependencies().has("http")); assert.ok(analyzeStringMock.haveBeenCalledTimes(1)); assert.ok(analyzeStringMock.haveBeenCalledWith("http")); }); diff --git a/test/probes/isRequire.spec.js b/test/probes/isRequire.spec.js index c63927a..9bb4fa0 100644 --- a/test/probes/isRequire.spec.js +++ b/test/probes/isRequire.spec.js @@ -17,7 +17,7 @@ test("it should ignore require CallExpression with no (zero) arguments", () => { assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.deepEqual(Object.keys(dependencies).length, 0); + assert.deepEqual(dependencies.size, 0); }); test("it should execute probe using require.resolve (detected by the VariableTracer)", () => { @@ -30,7 +30,7 @@ test("it should execute probe using require.resolve (detected by the VariableTra assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.ok("http" in dependencies); + assert.ok(dependencies.has("http")); }); test("it should execute probe using process.mainModule.require (detected by the VariableTracer)", () => { @@ -43,7 +43,7 @@ test("it should execute probe using process.mainModule.require (detected by the assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.ok("http" in dependencies); + assert.ok(dependencies.has("http")); }); test("it should execute probe on a variable reassignments of require (detected by the VariableTracer)", () => { @@ -59,8 +59,8 @@ test("it should execute probe on a variable reassignments of require (detected b assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("http" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("http")); }); test("it should execute probe on a variable reassignments of require extended (detected by the VariableTracer)", () => { @@ -78,9 +78,9 @@ test("it should execute probe on a variable reassignments of require extended (d assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 2); - assert.ok("http" in dependencies); - assert.ok("fs" in dependencies); + assert.strictEqual(dependencies.size, 2); + assert.ok(dependencies.has("http")); + assert.ok(dependencies.has("fs")); }); test("it should catch require with an Identifier argument pointing to the Node.js core http module", () => { @@ -94,8 +94,8 @@ test("it should catch require with an Identifier argument pointing to the Node.j assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("http" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("http")); }); test("it should throw an 'unsafe-import' warning for a require with an unknown Identifier", () => { @@ -134,8 +134,8 @@ test("it should catch require with a Literal argument having for value the Node. assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("http" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("http")); }); test(`it should catch require with an ArrayExpression where all Literals values concatened @@ -149,8 +149,8 @@ are equal to the Node.js core http module`, () => { assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("http" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("http")); }); test(`it should catch require with an ArrayExpression where all Literals values concatened @@ -165,8 +165,8 @@ are equal to the Node.js core http module (with charCodes as values)`, () => { assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("hello" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("hello")); }); test(`it should catch require with an ArrayExpression where all Literals values concatened @@ -183,8 +183,8 @@ are equal to the Node.js core http module (with VariableTracer usage)`, () => { assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("http" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("http")); }); test(`it should throw an 'unsafe-import' warning for using an ArrayExpression as require argument @@ -212,8 +212,8 @@ are equal to the Node.js core http module`, () => { assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("http" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("http")); }); test(`it should catch require with a BinaryExpression where all level of operands flattened @@ -230,8 +230,8 @@ are equal to the Node.js core http module (with VariableTracer usage)`, () => { assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("http" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("http")); }); test(`it should throw an 'unsafe-import' warning for using a BinaryExpression @@ -277,7 +277,7 @@ test("(require CallExpression): it should always throw an 'unsafe-import' warnin assert.strictEqual(warning.kind, "unsafe-import"); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 0); + assert.strictEqual(dependencies.size, 0); }); test(`(require CallExpression): it should catch a CallExpression containing an hexadecimal value @@ -298,8 +298,8 @@ and then add the unobfuscated value in the dependency list`, () => { assert.strictEqual(warning.kind, "unsafe-import"); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("http" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("http")); }); test("(require CallExpression): it should detect MemberExpression Buffer.from", () => { @@ -315,8 +315,8 @@ test("(require CallExpression): it should detect MemberExpression Buffer.from", assert.strictEqual(warning.kind, "unsafe-import"); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("http" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("http")); }); test("(require CallExpression): it should detect MemberExpression Buffer.from (with ArrayExpression argument)", () => { @@ -332,8 +332,8 @@ test("(require CallExpression): it should detect MemberExpression Buffer.from (w assert.strictEqual(warning.kind, "unsafe-import"); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("hello" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("hello")); }); test("(require CallExpression): it should detect MemberExpression require.resolve with a Literal value", () => { @@ -353,8 +353,8 @@ test("(require CallExpression): it should detect MemberExpression require.resolv assert.strictEqual(warning.kind, "unsafe-import"); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("foo" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("foo")); }); test("(require CallExpression): it should detect obfuscated atob value", () => { @@ -370,7 +370,7 @@ test("(require CallExpression): it should detect obfuscated atob value", () => { assert.strictEqual(sastAnalysis.warnings().length, 0); const dependencies = sastAnalysis.dependencies(); - assert.strictEqual(Object.keys(dependencies).length, 1); - assert.ok("os" in dependencies); + assert.strictEqual(dependencies.size, 1); + assert.ok(dependencies.has("os")); }); diff --git a/test/runASTAnalysis.spec.js b/test/runASTAnalysis.spec.js index fb4281b..7beadb3 100644 --- a/test/runASTAnalysis.spec.js +++ b/test/runASTAnalysis.spec.js @@ -25,7 +25,7 @@ test("it should return all dependencies required at runtime", () => { `, { module: false }); assert.strictEqual(warnings.length, 0); - assert.deepEqual([...dependencies], + assert.deepEqual([...dependencies.keys()], ["http", "net", "fs", "assert", "timers", "./aFile.js", "path"] ); }); @@ -92,7 +92,7 @@ test("it should be capable to follow a malicious code with hexa computation and "unsafe-import", "unsafe-stmt" ].sort()); - assert.deepEqual([...dependencies], ["./test/data"]); + assert.deepEqual([...dependencies.keys()], ["./test/data"]); }); test("it should throw a 'short-identifiers' warning for a code with only one-character identifiers", () => { @@ -109,15 +109,15 @@ test("it should throw a 'short-identifiers' warning for a code with only one-cha }); test("it should detect dependency required under a TryStatement", () => { - const { dependencies: deps } = runASTAnalysis(` + const { dependencies } = runASTAnalysis(` try { require("http"); } catch {} `); - assert.ok(Reflect.has(deps.dependencies, "http")); - assert.ok(deps.dependencies.http.inTry); + assert.ok(dependencies.has("http")); + assert.ok(dependencies.get("http").inTry); }); test("it should return isOneLineRequire true given a single line CJS export", () => { @@ -126,5 +126,5 @@ test("it should return isOneLineRequire true given a single line CJS export", () ); assert.ok(isOneLineRequire); - assert.deepEqual([...dependencies], ["foo"]); + assert.deepEqual([...dependencies.keys()], ["foo"]); }); diff --git a/test/runASTAnalysisOnFile.spec.js b/test/runASTAnalysisOnFile.spec.js index 4a4b95f..542439a 100644 --- a/test/runASTAnalysisOnFile.spec.js +++ b/test/runASTAnalysisOnFile.spec.js @@ -16,7 +16,7 @@ test("it remove the packageName from the dependencies list", async() => { assert.ok(result.ok); assert.strictEqual(result.warnings.length, 0); - assert.deepEqual([...result.dependencies], + assert.deepEqual([...result.dependencies.keys()], ["open"] ); }); diff --git a/test/utils/index.js b/test/utils/index.js index 161df34..22f5697 100644 --- a/test/utils/index.js +++ b/test/utils/index.js @@ -1,5 +1,5 @@ import * as meriyah from "meriyah"; -import Analysis from "../../src/Analysis.js"; +import { SourceFile } from "../../src/SourceFile.js"; import { walk } from "estree-walker"; export function getWarningKind(warnings) { @@ -50,9 +50,12 @@ function runOnProbes(node, analysis, probe) { return null; } -export function getSastAnalysis(strSource, probe) { +export function getSastAnalysis( + sourceCodeString, + probe +) { return { - analysis: new Analysis(), + analysis: new SourceFile(sourceCodeString), getWarning(warning) { return this.analysis.warnings.find( (item) => item.kind === warning @@ -62,11 +65,10 @@ export function getSastAnalysis(strSource, probe) { return this.analysis.warnings; }, dependencies() { - return this.analysis.dependencies.dependencies; + return this.analysis.dependencies; }, execute(body) { const self = this; - this.analysis.analyzeSourceString(strSource); walk(body, { enter(node) { diff --git a/types/api.d.ts b/types/api.d.ts index 04b5f4e..5f6a3af 100644 --- a/types/api.d.ts +++ b/types/api.d.ts @@ -1,4 +1,3 @@ -import { ASTDeps } from "./astdeps.js"; import { Warning } from "./warnings.js"; export { @@ -9,7 +8,27 @@ export { RuntimeFileOptions, Report, - ReportOnFile + ReportOnFile, + + SourceLocation, + Dependency +} + +interface SourceLocation { + start: { + line: number; + column: number; + }; + end: { + line: number; + column: number; + } +} + +interface Dependency { + unsafe: boolean; + inTry: boolean; + location?: null | SourceLocation; } interface RuntimeOptions { @@ -28,7 +47,7 @@ interface RuntimeOptions { } interface Report { - dependencies: ASTDeps; + dependencies: Map; warnings: Warning[]; idsLengthAvg: number; stringScore: number; @@ -50,7 +69,7 @@ interface RuntimeFileOptions { type ReportOnFile = { ok: true, warnings: Warning[]; - dependencies: ASTDeps; + dependencies: Map; isMinified: boolean; } | { ok: false, diff --git a/types/astdeps.d.ts b/types/astdeps.d.ts deleted file mode 100644 index ee18bf7..0000000 --- a/types/astdeps.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -export { - ASTDeps, - SourceLocation, - Dependency -} - -interface SourceLocation { - start: { - line: number; - column: number; - }; - end: { - line: number; - column: number; - } -} - -interface Dependency { - unsafe: boolean; - inTry: boolean; - location?: SourceLocation; -} - -declare class ASTDeps { - constructor(); - removeByName(name: string): void; - add(depName: string): void; - getDependenciesInTryStatement(): IterableIterator; - [Symbol.iterator]: IterableIterator; - - public isInTryStmt: boolean; - public dependencies: Record; - public readonly size: number; -}