@nodesecure/js-x-ray 7.1.0 → 7.1.1
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 +18 -5
- package/index.d.ts +2 -0
- package/index.js +4 -1
- package/package.json +1 -1
- package/src/AstAnalyser.js +137 -137
- package/src/EntryFilesAnalyser.js +99 -99
- package/types/api.d.ts +6 -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
|
|
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
|
@@ -1,137 +1,137 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
// Import Third-party Dependencies
|
|
6
|
-
import { walk } from "estree-walker";
|
|
7
|
-
import isMinified from "is-minified-code";
|
|
8
|
-
|
|
9
|
-
// Import Internal Dependencies
|
|
10
|
-
import { SourceFile } from "./SourceFile.js";
|
|
11
|
-
import { isOneLineExpressionExport } from "./utils/index.js";
|
|
12
|
-
import { JsSourceParser } from "./JsSourceParser.js";
|
|
13
|
-
|
|
14
|
-
export class AstAnalyser {
|
|
15
|
-
/**
|
|
16
|
-
* @constructor
|
|
17
|
-
* @param {object} [options={}]
|
|
18
|
-
* @param {SourceParser} [options.customParser]
|
|
19
|
-
* @param {Array<object>} [options.customProbes]
|
|
20
|
-
* @param {boolean} [options.skipDefaultProbes=false]
|
|
21
|
-
*/
|
|
22
|
-
constructor(options = {}) {
|
|
23
|
-
this.parser = options.customParser ?? new JsSourceParser();
|
|
24
|
-
this.probesOptions = {
|
|
25
|
-
customProbes: options.customProbes ?? [],
|
|
26
|
-
skipDefaultProbes: options.skipDefaultProbes ?? false
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
analyse(str, options = Object.create(null)) {
|
|
31
|
-
const {
|
|
32
|
-
isMinified = false,
|
|
33
|
-
module = true,
|
|
34
|
-
removeHTMLComments = false
|
|
35
|
-
} = options;
|
|
36
|
-
|
|
37
|
-
const body = this.parser.parse(this.prepareSource(str, { removeHTMLComments }), {
|
|
38
|
-
isEcmaScriptModule: Boolean(module)
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const source = new SourceFile(str, this.probesOptions);
|
|
42
|
-
|
|
43
|
-
// we walk each AST Nodes, this is a purely synchronous I/O
|
|
44
|
-
walk(body, {
|
|
45
|
-
enter(node) {
|
|
46
|
-
// Skip the root of the AST.
|
|
47
|
-
if (Array.isArray(node)) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const action = source.walk(node);
|
|
52
|
-
if (action === "skip") {
|
|
53
|
-
this.skip();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
...source.getResult(isMinified),
|
|
60
|
-
dependencies: source.dependencies,
|
|
61
|
-
isOneLineRequire: isOneLineExpressionExport(body)
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async analyseFile(
|
|
66
|
-
pathToFile,
|
|
67
|
-
options = {}
|
|
68
|
-
) {
|
|
69
|
-
try {
|
|
70
|
-
const {
|
|
71
|
-
packageName = null,
|
|
72
|
-
module = true,
|
|
73
|
-
removeHTMLComments = false
|
|
74
|
-
} = options;
|
|
75
|
-
|
|
76
|
-
const str = await fs.readFile(pathToFile, "utf-8");
|
|
77
|
-
const filePathString = pathToFile instanceof URL ? pathToFile.href : pathToFile;
|
|
78
|
-
|
|
79
|
-
const isMin = filePathString.includes(".min") || isMinified(str);
|
|
80
|
-
const data = this.analyse(str, {
|
|
81
|
-
isMinified: isMin,
|
|
82
|
-
module: path.extname(filePathString) === ".mjs" ? true : module,
|
|
83
|
-
removeHTMLComments
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
if (packageName !== null) {
|
|
87
|
-
data.dependencies.delete(packageName);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
ok: true,
|
|
92
|
-
dependencies: data.dependencies,
|
|
93
|
-
warnings: data.warnings,
|
|
94
|
-
isMinified: !data.isOneLineRequire && isMin
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
return {
|
|
99
|
-
ok: false,
|
|
100
|
-
warnings: [
|
|
101
|
-
{ kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
|
|
102
|
-
]
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @param {!string} source
|
|
109
|
-
* @param {object} options
|
|
110
|
-
* @param {boolean} [options.removeHTMLComments=false]
|
|
111
|
-
*/
|
|
112
|
-
prepareSource(source, options = {}) {
|
|
113
|
-
if (typeof source !== "string") {
|
|
114
|
-
throw new TypeError("source must be a string");
|
|
115
|
-
}
|
|
116
|
-
const { removeHTMLComments = false } = options;
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* if the file start with a shebang then we remove it because meriyah.parseScript fail to parse it.
|
|
120
|
-
* @example
|
|
121
|
-
* #!/usr/bin/env node
|
|
122
|
-
*/
|
|
123
|
-
const rawNoShebang = source.startsWith("#") ?
|
|
124
|
-
source.slice(source.indexOf("\n") + 1) : source;
|
|
125
|
-
|
|
126
|
-
return removeHTMLComments ?
|
|
127
|
-
this.#removeHTMLComment(rawNoShebang) : rawNoShebang;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* @param {!string} str
|
|
132
|
-
* @returns {string}
|
|
133
|
-
*/
|
|
134
|
-
#removeHTMLComment(str) {
|
|
135
|
-
return str.replaceAll(/<!--[\s\S]*?(?:-->)/g, "");
|
|
136
|
-
}
|
|
137
|
-
}
|
|
1
|
+
// Import Node.js Dependencies
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
// Import Third-party Dependencies
|
|
6
|
+
import { walk } from "estree-walker";
|
|
7
|
+
import isMinified from "is-minified-code";
|
|
8
|
+
|
|
9
|
+
// Import Internal Dependencies
|
|
10
|
+
import { SourceFile } from "./SourceFile.js";
|
|
11
|
+
import { isOneLineExpressionExport } from "./utils/index.js";
|
|
12
|
+
import { JsSourceParser } from "./JsSourceParser.js";
|
|
13
|
+
|
|
14
|
+
export class AstAnalyser {
|
|
15
|
+
/**
|
|
16
|
+
* @constructor
|
|
17
|
+
* @param {object} [options={}]
|
|
18
|
+
* @param {SourceParser} [options.customParser]
|
|
19
|
+
* @param {Array<object>} [options.customProbes]
|
|
20
|
+
* @param {boolean} [options.skipDefaultProbes=false]
|
|
21
|
+
*/
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.parser = options.customParser ?? new JsSourceParser();
|
|
24
|
+
this.probesOptions = {
|
|
25
|
+
customProbes: options.customProbes ?? [],
|
|
26
|
+
skipDefaultProbes: options.skipDefaultProbes ?? false
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
analyse(str, options = Object.create(null)) {
|
|
31
|
+
const {
|
|
32
|
+
isMinified = false,
|
|
33
|
+
module = true,
|
|
34
|
+
removeHTMLComments = false
|
|
35
|
+
} = options;
|
|
36
|
+
|
|
37
|
+
const body = this.parser.parse(this.prepareSource(str, { removeHTMLComments }), {
|
|
38
|
+
isEcmaScriptModule: Boolean(module)
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const source = new SourceFile(str, this.probesOptions);
|
|
42
|
+
|
|
43
|
+
// we walk each AST Nodes, this is a purely synchronous I/O
|
|
44
|
+
walk(body, {
|
|
45
|
+
enter(node) {
|
|
46
|
+
// Skip the root of the AST.
|
|
47
|
+
if (Array.isArray(node)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const action = source.walk(node);
|
|
52
|
+
if (action === "skip") {
|
|
53
|
+
this.skip();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...source.getResult(isMinified),
|
|
60
|
+
dependencies: source.dependencies,
|
|
61
|
+
isOneLineRequire: isOneLineExpressionExport(body)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async analyseFile(
|
|
66
|
+
pathToFile,
|
|
67
|
+
options = {}
|
|
68
|
+
) {
|
|
69
|
+
try {
|
|
70
|
+
const {
|
|
71
|
+
packageName = null,
|
|
72
|
+
module = true,
|
|
73
|
+
removeHTMLComments = false
|
|
74
|
+
} = options;
|
|
75
|
+
|
|
76
|
+
const str = await fs.readFile(pathToFile, "utf-8");
|
|
77
|
+
const filePathString = pathToFile instanceof URL ? pathToFile.href : pathToFile;
|
|
78
|
+
|
|
79
|
+
const isMin = filePathString.includes(".min") || isMinified(str);
|
|
80
|
+
const data = this.analyse(str, {
|
|
81
|
+
isMinified: isMin,
|
|
82
|
+
module: path.extname(filePathString) === ".mjs" ? true : module,
|
|
83
|
+
removeHTMLComments
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (packageName !== null) {
|
|
87
|
+
data.dependencies.delete(packageName);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
ok: true,
|
|
92
|
+
dependencies: data.dependencies,
|
|
93
|
+
warnings: data.warnings,
|
|
94
|
+
isMinified: !data.isOneLineRequire && isMin
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
warnings: [
|
|
101
|
+
{ kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
|
|
102
|
+
]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {!string} source
|
|
109
|
+
* @param {object} options
|
|
110
|
+
* @param {boolean} [options.removeHTMLComments=false]
|
|
111
|
+
*/
|
|
112
|
+
prepareSource(source, options = {}) {
|
|
113
|
+
if (typeof source !== "string") {
|
|
114
|
+
throw new TypeError("source must be a string");
|
|
115
|
+
}
|
|
116
|
+
const { removeHTMLComments = false } = options;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* if the file start with a shebang then we remove it because meriyah.parseScript fail to parse it.
|
|
120
|
+
* @example
|
|
121
|
+
* #!/usr/bin/env node
|
|
122
|
+
*/
|
|
123
|
+
const rawNoShebang = source.startsWith("#") ?
|
|
124
|
+
source.slice(source.indexOf("\n") + 1) : source;
|
|
125
|
+
|
|
126
|
+
return removeHTMLComments ?
|
|
127
|
+
this.#removeHTMLComment(rawNoShebang) : rawNoShebang;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {!string} str
|
|
132
|
+
* @returns {string}
|
|
133
|
+
*/
|
|
134
|
+
#removeHTMLComment(str) {
|
|
135
|
+
return str.replaceAll(/<!--[\s\S]*?(?:-->)/g, "");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
// Import Internal Dependencies
|
|
6
|
-
import { AstAnalyser } from "./AstAnalyser.js";
|
|
7
|
-
|
|
8
|
-
const kDefaultExtensions = ["js", "cjs", "mjs", "node"];
|
|
9
|
-
|
|
10
|
-
export class EntryFilesAnalyser {
|
|
11
|
-
/**
|
|
12
|
-
* @constructor
|
|
13
|
-
* @param {object} [options={}]
|
|
14
|
-
* @param {AstAnalyser} [options.astAnalyzer=new AstAnalyser()]
|
|
15
|
-
* @param {function} [options.loadExtensions]
|
|
16
|
-
*/
|
|
17
|
-
constructor(options = {}) {
|
|
18
|
-
this.astAnalyzer = options.astAnalyzer ?? new AstAnalyser();
|
|
19
|
-
this.allowedExtensions = options.loadExtensions
|
|
20
|
-
? options.loadExtensions(kDefaultExtensions)
|
|
21
|
-
: kDefaultExtensions;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Asynchronously analyze a set of entry files yielding analysis reports.
|
|
26
|
-
*
|
|
27
|
-
* @param {(string | URL)[]} entryFiles
|
|
28
|
-
* @yields {Object} - Yields an object containing the analysis report for each file.
|
|
29
|
-
*/
|
|
30
|
-
async* analyse(entryFiles) {
|
|
31
|
-
this.analyzedDeps = new Set();
|
|
32
|
-
|
|
33
|
-
for (const file of entryFiles) {
|
|
34
|
-
yield* this.#analyzeFile(file);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async* #analyzeFile(file) {
|
|
39
|
-
const filePath = file instanceof URL ? file.pathname : file;
|
|
40
|
-
const report = await this.astAnalyzer.analyseFile(file);
|
|
41
|
-
|
|
42
|
-
yield { url: filePath, ...report };
|
|
43
|
-
|
|
44
|
-
if (!report.ok) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
yield* this.#analyzeDeps(report.dependencies, path.dirname(filePath));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async* #analyzeDeps(deps, basePath) {
|
|
52
|
-
for (const [name] of deps) {
|
|
53
|
-
const depPath = await this.#getInternalDepPath(name, basePath);
|
|
54
|
-
if (depPath && !this.analyzedDeps.has(depPath)) {
|
|
55
|
-
this.analyzedDeps.add(depPath);
|
|
56
|
-
|
|
57
|
-
yield* this.#analyzeFile(depPath);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async #getInternalDepPath(name, basePath) {
|
|
63
|
-
const depPath = path.join(basePath, name);
|
|
64
|
-
const existingExt = path.extname(name);
|
|
65
|
-
if (existingExt !== "") {
|
|
66
|
-
if (!this.allowedExtensions.includes(existingExt.slice(1))) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (await this.#fileExists(depPath)) {
|
|
71
|
-
return depPath;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
for (const ext of this.allowedExtensions) {
|
|
76
|
-
const depPathWithExt = `${depPath}.${ext}`;
|
|
77
|
-
if (await this.#fileExists(depPathWithExt)) {
|
|
78
|
-
return depPathWithExt;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async #fileExists(path) {
|
|
86
|
-
try {
|
|
87
|
-
await fs.access(path, fs.constants.F_OK);
|
|
88
|
-
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
if (error.code !== "ENOENT") {
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
1
|
+
// Import Node.js Dependencies
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
// Import Internal Dependencies
|
|
6
|
+
import { AstAnalyser } from "./AstAnalyser.js";
|
|
7
|
+
|
|
8
|
+
const kDefaultExtensions = ["js", "cjs", "mjs", "node"];
|
|
9
|
+
|
|
10
|
+
export class EntryFilesAnalyser {
|
|
11
|
+
/**
|
|
12
|
+
* @constructor
|
|
13
|
+
* @param {object} [options={}]
|
|
14
|
+
* @param {AstAnalyser} [options.astAnalyzer=new AstAnalyser()]
|
|
15
|
+
* @param {function} [options.loadExtensions]
|
|
16
|
+
*/
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.astAnalyzer = options.astAnalyzer ?? new AstAnalyser();
|
|
19
|
+
this.allowedExtensions = options.loadExtensions
|
|
20
|
+
? options.loadExtensions(kDefaultExtensions)
|
|
21
|
+
: kDefaultExtensions;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Asynchronously analyze a set of entry files yielding analysis reports.
|
|
26
|
+
*
|
|
27
|
+
* @param {(string | URL)[]} entryFiles
|
|
28
|
+
* @yields {Object} - Yields an object containing the analysis report for each file.
|
|
29
|
+
*/
|
|
30
|
+
async* analyse(entryFiles) {
|
|
31
|
+
this.analyzedDeps = new Set();
|
|
32
|
+
|
|
33
|
+
for (const file of entryFiles) {
|
|
34
|
+
yield* this.#analyzeFile(file);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async* #analyzeFile(file) {
|
|
39
|
+
const filePath = file instanceof URL ? file.pathname : file;
|
|
40
|
+
const report = await this.astAnalyzer.analyseFile(file);
|
|
41
|
+
|
|
42
|
+
yield { url: filePath, ...report };
|
|
43
|
+
|
|
44
|
+
if (!report.ok) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
yield* this.#analyzeDeps(report.dependencies, path.dirname(filePath));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async* #analyzeDeps(deps, basePath) {
|
|
52
|
+
for (const [name] of deps) {
|
|
53
|
+
const depPath = await this.#getInternalDepPath(name, basePath);
|
|
54
|
+
if (depPath && !this.analyzedDeps.has(depPath)) {
|
|
55
|
+
this.analyzedDeps.add(depPath);
|
|
56
|
+
|
|
57
|
+
yield* this.#analyzeFile(depPath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async #getInternalDepPath(name, basePath) {
|
|
63
|
+
const depPath = path.join(basePath, name);
|
|
64
|
+
const existingExt = path.extname(name);
|
|
65
|
+
if (existingExt !== "") {
|
|
66
|
+
if (!this.allowedExtensions.includes(existingExt.slice(1))) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (await this.#fileExists(depPath)) {
|
|
71
|
+
return depPath;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const ext of this.allowedExtensions) {
|
|
76
|
+
const depPathWithExt = `${depPath}.${ext}`;
|
|
77
|
+
if (await this.#fileExists(depPathWithExt)) {
|
|
78
|
+
return depPathWithExt;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async #fileExists(path) {
|
|
86
|
+
try {
|
|
87
|
+
await fs.access(path, fs.constants.F_OK);
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (error.code !== "ENOENT") {
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
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,
|
|
@@ -101,7 +104,7 @@ interface SourceParser {
|
|
|
101
104
|
declare class AstAnalyser {
|
|
102
105
|
constructor(options?: AstAnalyserOptions);
|
|
103
106
|
analyse: (str: string, options?: RuntimeOptions) => Report;
|
|
104
|
-
|
|
107
|
+
analyseFile(pathToFile: string, options?: RuntimeFileOptions): Promise<ReportOnFile>;
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
interface EntryFilesAnalyserOptions {
|
|
@@ -118,5 +121,7 @@ declare class EntryFilesAnalyser {
|
|
|
118
121
|
analyse(entryFiles: (string | URL)[]): AsyncGenerator<ReportOnFile & { url: string }>;
|
|
119
122
|
}
|
|
120
123
|
|
|
124
|
+
declare class JsSourceParser implements SourceParser {}
|
|
125
|
+
|
|
121
126
|
declare function runASTAnalysis(str: string, options?: RuntimeOptions & AstAnalyserOptions): Report;
|
|
122
127
|
declare function runASTAnalysisOnFile(pathToFile: string, options?: RuntimeFileOptions & AstAnalyserOptions): Promise<ReportOnFile>;
|