@nodesecure/js-x-ray 7.1.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -67,11 +67,13 @@ require(Buffer.from("6673", "hex").toString());
67
67
 
68
68
  Then use `js-x-ray` to run an analysis of the JavaScript code:
69
69
  ```js
70
- import { runASTAnalysis } from "@nodesecure/js-x-ray";
70
+ import { AstAnalyser } from "@nodesecure/js-x-ray";
71
71
  import { readFileSync } from "node:fs";
72
72
 
73
- const { warnings, dependencies } = runASTAnalysis(
74
- readFileSync("./file.js", "utf-8")
73
+ const scanner = new AstAnalyser();
74
+
75
+ const { warnings, dependencies } = await scanner.analyseFile(
76
+ "./file.js"
75
77
  );
76
78
 
77
79
  console.log(dependencies);
@@ -174,11 +176,16 @@ You can pass an array of probes to the `runASTAnalysis/runASTAnalysisOnFile` fun
174
176
  Here using the example probe upper:
175
177
 
176
178
  ```ts
177
- import { runASTAnalysis } from "@nodesecure/js-x-ray";
179
+ import { AstAnalyser } from "@nodesecure/js-x-ray";
178
180
 
179
181
  // add your customProbes here (see example above)
180
182
 
181
- const result = runASTAnalysis("const danger = 'danger';", { customProbes, skipDefaultProbes: true });
183
+ const scanner = new AstAnalyser({
184
+ customProbes,
185
+ skipDefaultProbes: true
186
+ });
187
+
188
+ const result = scanner.analyse("const danger = 'danger';");
182
189
 
183
190
  console.log(result);
184
191
  ```
@@ -202,6 +209,12 @@ Congrats, you have created your first custom probe! 🎉
202
209
  > Check the types in [index.d.ts](index.d.ts) and [types/api.d.ts](types/api.d.ts) for more details about the `options`
203
210
 
204
211
  ## API
212
+
213
+ - [AstAnalyser](./docs/api/AstAnalyser.md)
214
+ - [EntryFilesAnalyser](./docs/api/EntryFilesAnalyser.md)
215
+
216
+ Legacy APIs waiting to be deprecated;
217
+
205
218
  <details>
206
219
  <summary>runASTAnalysis(str: string, options?: RuntimeOptions & AstAnalyserOptions): Report</summary>
207
220
 
@@ -210,6 +223,8 @@ interface RuntimeOptions {
210
223
  module?: boolean;
211
224
  removeHTMLComments?: boolean;
212
225
  isMinified?: boolean;
226
+ initialize?: (sourceFile: SourceFile) => void;
227
+ finalize?: (sourceFile: SourceFile) => void;
213
228
  }
214
229
  ```
215
230
 
@@ -243,6 +258,8 @@ interface RuntimeFileOptions {
243
258
  module?: boolean;
244
259
  removeHTMLComments?: boolean;
245
260
  packageName?: string;
261
+ initialize?: (sourceFile: SourceFile) => void;
262
+ finalize?: (sourceFile: SourceFile) => void;
246
263
  }
247
264
  ```
248
265
 
package/index.d.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  EntryFilesAnalyserOptions,
7
7
 
8
8
  SourceParser,
9
+ JsSourceParser,
9
10
  runASTAnalysis,
10
11
  runASTAnalysisOnFile,
11
12
  Report,
@@ -31,6 +32,7 @@ export {
31
32
  AstAnalyserOptions,
32
33
  EntryFilesAnalyser,
33
34
  EntryFilesAnalyserOptions,
35
+ JsSourceParser,
34
36
  SourceParser,
35
37
  runASTAnalysis,
36
38
  runASTAnalysisOnFile,
package/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { warnings } from "./src/warnings.js";
3
3
  import { JsSourceParser } from "./src/JsSourceParser.js";
4
4
  import { AstAnalyser } from "./src/AstAnalyser.js";
5
+ import { EntryFilesAnalyser } from "./src/EntryFilesAnalyser.js";
5
6
 
6
7
  function runASTAnalysis(
7
8
  str,
@@ -28,8 +29,8 @@ async function runASTAnalysisOnFile(
28
29
  options = {}
29
30
  ) {
30
31
  const {
31
- customParser = new JsSourceParser(),
32
32
  customProbes = [],
33
+ customParser = new JsSourceParser(),
33
34
  skipDefaultProbes = false,
34
35
  ...opts
35
36
  } = options;
@@ -46,6 +47,8 @@ async function runASTAnalysisOnFile(
46
47
  export {
47
48
  warnings,
48
49
  AstAnalyser,
50
+ EntryFilesAnalyser,
51
+ JsSourceParser,
49
52
  runASTAnalysis,
50
53
  runASTAnalysisOnFile
51
54
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodesecure/js-x-ray",
3
- "version": "7.1.0",
3
+ "version": "7.2.0",
4
4
  "description": "JavaScript AST XRay analysis",
5
5
  "type": "module",
6
6
  "exports": "./index.js",
@@ -31,15 +31,25 @@ export class AstAnalyser {
31
31
  const {
32
32
  isMinified = false,
33
33
  module = true,
34
- removeHTMLComments = false
34
+ removeHTMLComments = false,
35
+ initialize,
36
+ finalize
35
37
  } = options;
36
38
 
37
39
  const body = this.parser.parse(this.prepareSource(str, { removeHTMLComments }), {
38
40
  isEcmaScriptModule: Boolean(module)
39
41
  });
40
-
41
42
  const source = new SourceFile(str, this.probesOptions);
42
43
 
44
+ // TODO: this check should be factorized in a way that we reuse it
45
+ // on analyze and anlyseFile
46
+ if (initialize) {
47
+ if (typeof initialize !== "function") {
48
+ throw new TypeError("options.initialize must be a function");
49
+ }
50
+ initialize(source);
51
+ }
52
+
43
53
  // we walk each AST Nodes, this is a purely synchronous I/O
44
54
  walk(body, {
45
55
  enter(node) {
@@ -55,6 +65,15 @@ export class AstAnalyser {
55
65
  }
56
66
  });
57
67
 
68
+ // TODO: this check should be factorized in a way that we reuse it
69
+ // on analyze and anlyseFile
70
+ if (finalize) {
71
+ if (typeof finalize !== "function") {
72
+ throw new TypeError("options.finalize must be a function");
73
+ }
74
+ finalize(source);
75
+ }
76
+
58
77
  return {
59
78
  ...source.getResult(isMinified),
60
79
  dependencies: source.dependencies,
@@ -70,7 +89,9 @@ export class AstAnalyser {
70
89
  const {
71
90
  packageName = null,
72
91
  module = true,
73
- removeHTMLComments = false
92
+ removeHTMLComments = false,
93
+ initialize,
94
+ finalize
74
95
  } = options;
75
96
 
76
97
  const str = await fs.readFile(pathToFile, "utf-8");
@@ -80,7 +101,9 @@ export class AstAnalyser {
80
101
  const data = this.analyse(str, {
81
102
  isMinified: isMin,
82
103
  module: path.extname(filePathString) === ".mjs" ? true : module,
83
- removeHTMLComments
104
+ removeHTMLComments,
105
+ initialize,
106
+ finalize
84
107
  });
85
108
 
86
109
  if (packageName !== null) {
@@ -1,10 +1,12 @@
1
1
  // Import Node.js Dependencies
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
 
5
6
  // Import Internal Dependencies
6
7
  import { AstAnalyser } from "./AstAnalyser.js";
7
8
 
9
+ // CONSTANTS
8
10
  const kDefaultExtensions = ["js", "cjs", "mjs", "node"];
9
11
 
10
12
  export class EntryFilesAnalyser {
@@ -16,9 +18,11 @@ export class EntryFilesAnalyser {
16
18
  */
17
19
  constructor(options = {}) {
18
20
  this.astAnalyzer = options.astAnalyzer ?? new AstAnalyser();
19
- this.allowedExtensions = options.loadExtensions
21
+ const rawAllowedExtensions = options.loadExtensions
20
22
  ? options.loadExtensions(kDefaultExtensions)
21
23
  : kDefaultExtensions;
24
+
25
+ this.allowedExtensions = new Set(rawAllowedExtensions);
22
26
  }
23
27
 
24
28
  /**
@@ -36,7 +40,7 @@ export class EntryFilesAnalyser {
36
40
  }
37
41
 
38
42
  async* #analyzeFile(file) {
39
- const filePath = file instanceof URL ? file.pathname : file;
43
+ const filePath = file instanceof URL ? fileURLToPath(file) : file;
40
44
  const report = await this.astAnalyzer.analyseFile(file);
41
45
 
42
46
  yield { url: filePath, ...report };
@@ -45,12 +49,16 @@ export class EntryFilesAnalyser {
45
49
  return;
46
50
  }
47
51
 
48
- yield* this.#analyzeDeps(report.dependencies, path.dirname(filePath));
52
+ yield* this.#analyzeDeps(
53
+ report.dependencies,
54
+ path.dirname(filePath)
55
+ );
49
56
  }
50
57
 
51
58
  async* #analyzeDeps(deps, basePath) {
52
59
  for (const [name] of deps) {
53
60
  const depPath = await this.#getInternalDepPath(name, basePath);
61
+
54
62
  if (depPath && !this.analyzedDeps.has(depPath)) {
55
63
  this.analyzedDeps.add(depPath);
56
64
 
@@ -62,20 +70,25 @@ export class EntryFilesAnalyser {
62
70
  async #getInternalDepPath(name, basePath) {
63
71
  const depPath = path.join(basePath, name);
64
72
  const existingExt = path.extname(name);
65
- if (existingExt !== "") {
66
- if (!this.allowedExtensions.includes(existingExt.slice(1))) {
67
- return null;
68
- }
69
73
 
70
- if (await this.#fileExists(depPath)) {
71
- return depPath;
74
+ if (existingExt === "") {
75
+ for (const ext of this.allowedExtensions) {
76
+ const depPathWithExt = `${depPath}.${ext}`;
77
+
78
+ const fileExist = await this.#fileExists(depPathWithExt);
79
+ if (fileExist) {
80
+ return depPathWithExt;
81
+ }
72
82
  }
73
83
  }
84
+ else {
85
+ if (!this.allowedExtensions.has(existingExt.slice(1))) {
86
+ return null;
87
+ }
74
88
 
75
- for (const ext of this.allowedExtensions) {
76
- const depPathWithExt = `${depPath}.${ext}`;
77
- if (await this.#fileExists(depPathWithExt)) {
78
- return depPathWithExt;
89
+ const fileExist = await this.#fileExists(depPath);
90
+ if (fileExist) {
91
+ return depPath;
79
92
  }
80
93
  }
81
94
 
@@ -84,7 +97,7 @@ export class EntryFilesAnalyser {
84
97
 
85
98
  async #fileExists(path) {
86
99
  try {
87
- await fs.access(path, fs.constants.F_OK);
100
+ await fs.access(path, fs.constants.R_OK);
88
101
 
89
102
  return true;
90
103
  }
package/types/api.d.ts CHANGED
@@ -4,8 +4,11 @@ import { Statement } from "meriyah/dist/src/estree.js";
4
4
  export {
5
5
  AstAnalyser,
6
6
  AstAnalyserOptions,
7
+
7
8
  EntryFilesAnalyser,
8
9
  EntryFilesAnalyserOptions,
10
+
11
+ JsSourceParser,
9
12
  SourceParser,
10
13
  runASTAnalysis,
11
14
  runASTAnalysisOnFile,
@@ -50,6 +53,8 @@ interface RuntimeOptions {
50
53
  * @default false
51
54
  */
52
55
  isMinified?: boolean;
56
+ initialize?: (sourceFile: SourceFile) => void;
57
+ finalize?: (sourceFile: SourceFile) => void;
53
58
  }
54
59
 
55
60
  interface RuntimeFileOptions extends Omit<RuntimeOptions, "isMinified"> {
@@ -101,7 +106,7 @@ interface SourceParser {
101
106
  declare class AstAnalyser {
102
107
  constructor(options?: AstAnalyserOptions);
103
108
  analyse: (str: string, options?: RuntimeOptions) => Report;
104
- analyzeFile(pathToFile: string, options?: RuntimeFileOptions): Promise<ReportOnFile>;
109
+ analyseFile(pathToFile: string, options?: RuntimeFileOptions): Promise<ReportOnFile>;
105
110
  }
106
111
 
107
112
  interface EntryFilesAnalyserOptions {
@@ -118,5 +123,7 @@ declare class EntryFilesAnalyser {
118
123
  analyse(entryFiles: (string | URL)[]): AsyncGenerator<ReportOnFile & { url: string }>;
119
124
  }
120
125
 
126
+ declare class JsSourceParser implements SourceParser {}
127
+
121
128
  declare function runASTAnalysis(str: string, options?: RuntimeOptions & AstAnalyserOptions): Report;
122
129
  declare function runASTAnalysisOnFile(pathToFile: string, options?: RuntimeFileOptions & AstAnalyserOptions): Promise<ReportOnFile>;