@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 +22 -5
- package/index.d.ts +2 -0
- package/index.js +4 -1
- package/package.json +1 -1
- package/src/AstAnalyser.js +27 -4
- package/src/EntryFilesAnalyser.js +27 -14
- package/types/api.d.ts +8 -1
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 {
|
|
70
|
+
import { AstAnalyser } from "@nodesecure/js-x-ray";
|
|
71
71
|
import { readFileSync } from "node:fs";
|
|
72
72
|
|
|
73
|
-
const
|
|
74
|
-
|
|
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 {
|
|
179
|
+
import { AstAnalyser } from "@nodesecure/js-x-ray";
|
|
178
180
|
|
|
179
181
|
// add your customProbes here (see example above)
|
|
180
182
|
|
|
181
|
-
const
|
|
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
package/src/AstAnalyser.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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.
|
|
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
|
-
|
|
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>;
|