Skip to content

Commit e59a1a5

Browse files
refactor: runASTAnalysis functions to use class AstAnalyser (#216)
* refactor: create and use AstAnalyser class to handle ast analysis * refactor: remove useless JSDoc * fix: ESTree type from meriyah * fix: add JSDoc again * fix: optimize regular expression by removing non-capturing group * fix: revert regex to the original pattern * fix: declare class AstAnalyser instead of interface * fix: replace Parser type by SourceParser type * fix: parsers should return body directly
1 parent 83aa3d5 commit e59a1a5

12 files changed

+472
-352
lines changed

index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
AstAnalyser,
3+
SourceParser,
24
runASTAnalysis,
35
runASTAnalysisOnFile,
46
Report,
@@ -20,6 +22,8 @@ declare const warnings: Record<WarningName, Pick<WarningDefault, "experimental"
2022

2123
export {
2224
warnings,
25+
AstAnalyser,
26+
SourceParser,
2327
runASTAnalysis,
2428
runASTAnalysisOnFile,
2529
Report,

index.js

Lines changed: 19 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,39 @@
1-
// Import Node.js Dependencies
2-
import fs from "fs/promises";
3-
import path from "path";
4-
5-
// Import Third-party Dependencies
6-
import { walk } from "estree-walker";
7-
import isMinified from "is-minified-code";
8-
91
// Import Internal Dependencies
10-
import { SourceFile } from "./src/SourceFile.js";
112
import { warnings } from "./src/warnings.js";
12-
import { isOneLineExpressionExport } from "./src/utils/index.js";
133
import { JsSourceParser } from "./src/JsSourceParser.js";
4+
import { AstAnalyser } from "./src/AstAnalyser.js";
145

15-
export function runASTAnalysis(
6+
function runASTAnalysis(
167
str,
178
options = Object.create(null)
189
) {
1910
const {
20-
module = true,
21-
isMinified = false,
22-
removeHTMLComments = false
11+
customParser = new JsSourceParser(),
12+
...opts
2313
} = options;
2414

25-
const parser = new JsSourceParser(str, { removeHTMLComments });
26-
const body = parser.parseScript({
27-
isEcmaScriptModule: Boolean(module)
28-
});
29-
30-
const source = new SourceFile(parser.raw);
31-
32-
// we walk each AST Nodes, this is a purely synchronous I/O
33-
walk(body, {
34-
enter(node) {
35-
// Skip the root of the AST.
36-
if (Array.isArray(node)) {
37-
return;
38-
}
15+
const analyser = new AstAnalyser(customParser);
3916

40-
const action = source.walk(node);
41-
if (action === "skip") {
42-
this.skip();
43-
}
44-
}
45-
});
46-
47-
return {
48-
...source.getResult(isMinified),
49-
dependencies: source.dependencies,
50-
isOneLineRequire: isOneLineExpressionExport(body)
51-
};
17+
return analyser.analyse(str, opts);
5218
}
5319

54-
export async function runASTAnalysisOnFile(
20+
async function runASTAnalysisOnFile(
5521
pathToFile,
5622
options = {}
5723
) {
58-
try {
59-
const {
60-
packageName = null,
61-
module = true,
62-
removeHTMLComments = false
63-
} = options;
64-
65-
const str = await fs.readFile(pathToFile, "utf-8");
66-
const filePathString = pathToFile instanceof URL ? pathToFile.href : pathToFile;
24+
const {
25+
customParser = new JsSourceParser(),
26+
...opts
27+
} = options;
6728

68-
const isMin = filePathString.includes(".min") || isMinified(str);
69-
const data = runASTAnalysis(str, {
70-
isMinified: isMin,
71-
module: path.extname(filePathString) === ".mjs" ? true : module,
72-
removeHTMLComments
73-
});
74-
if (packageName !== null) {
75-
data.dependencies.delete(packageName);
76-
}
29+
const analyser = new AstAnalyser(customParser);
7730

78-
return {
79-
ok: true,
80-
dependencies: data.dependencies,
81-
warnings: data.warnings,
82-
isMinified: !data.isOneLineRequire && isMin
83-
};
84-
}
85-
catch (error) {
86-
return {
87-
ok: false,
88-
warnings: [
89-
{ kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
90-
]
91-
};
92-
}
31+
return analyser.analyseFile(pathToFile, opts);
9332
}
9433

95-
export { warnings };
34+
export {
35+
warnings,
36+
AstAnalyser,
37+
runASTAnalysis,
38+
runASTAnalysisOnFile
39+
};

src/AstAnalyser.js

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
13+
export class AstAnalyser {
14+
/**
15+
* @constructor
16+
* @param { SourceParser } parser
17+
*/
18+
constructor(parser) {
19+
this.parser = parser;
20+
}
21+
22+
analyse(str, options = Object.create(null)) {
23+
const {
24+
isMinified = false,
25+
module = true,
26+
removeHTMLComments = false
27+
} = options;
28+
29+
const body = this.parser.parse(this.prepareSource(str, { removeHTMLComments }), {
30+
isEcmaScriptModule: Boolean(module)
31+
});
32+
33+
const source = new SourceFile(str);
34+
35+
// we walk each AST Nodes, this is a purely synchronous I/O
36+
walk(body, {
37+
enter(node) {
38+
// Skip the root of the AST.
39+
if (Array.isArray(node)) {
40+
return;
41+
}
42+
43+
const action = source.walk(node);
44+
if (action === "skip") {
45+
this.skip();
46+
}
47+
}
48+
});
49+
50+
return {
51+
...source.getResult(isMinified),
52+
dependencies: source.dependencies,
53+
isOneLineRequire: isOneLineExpressionExport(body)
54+
};
55+
}
56+
57+
async analyseFile(
58+
pathToFile,
59+
options = {}
60+
) {
61+
try {
62+
const {
63+
packageName = null,
64+
module = true,
65+
removeHTMLComments = false
66+
} = options;
67+
68+
const str = await fs.readFile(pathToFile, "utf-8");
69+
const filePathString = pathToFile instanceof URL ? pathToFile.href : pathToFile;
70+
71+
const isMin = filePathString.includes(".min") || isMinified(str);
72+
const data = this.analyse(str, {
73+
isMinified: isMin,
74+
module: path.extname(filePathString) === ".mjs" ? true : module,
75+
removeHTMLComments
76+
});
77+
78+
if (packageName !== null) {
79+
data.dependencies.delete(packageName);
80+
}
81+
82+
return {
83+
ok: true,
84+
dependencies: data.dependencies,
85+
warnings: data.warnings,
86+
isMinified: !data.isOneLineRequire && isMin
87+
};
88+
}
89+
catch (error) {
90+
return {
91+
ok: false,
92+
warnings: [
93+
{ kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
94+
]
95+
};
96+
}
97+
}
98+
99+
/**
100+
* @param {!string} source
101+
* @param {object} options
102+
* @param {boolean} [options.removeHTMLComments=false]
103+
*/
104+
prepareSource(source, options = {}) {
105+
if (typeof source !== "string") {
106+
throw new TypeError("source must be a string");
107+
}
108+
const { removeHTMLComments = false } = options;
109+
110+
/**
111+
* if the file start with a shebang then we remove it because meriyah.parseScript fail to parse it.
112+
* @example
113+
* #!/usr/bin/env node
114+
*/
115+
const rawNoShebang = source.startsWith("#") ?
116+
source.slice(source.indexOf("\n") + 1) : source;
117+
118+
return removeHTMLComments ?
119+
this.#removeHTMLComment(rawNoShebang) : rawNoShebang;
120+
}
121+
122+
/**
123+
* @param {!string} str
124+
* @returns {string}
125+
*/
126+
#removeHTMLComment(str) {
127+
return str.replaceAll(/<!--[\s\S]*?(?:-->)/g, "");
128+
}
129+
}

src/JsSourceParser.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
// Import Third-party Dependencies
22
import * as meriyah from "meriyah";
33

4-
// Import Internal Dependencies
5-
import { SourceParser } from "./SourceParser.js";
6-
74
// CONSTANTS
85
const kParsingOptions = {
96
next: true,
@@ -12,19 +9,19 @@ const kParsingOptions = {
129
jsx: true
1310
};
1411

15-
export class JsSourceParser extends SourceParser {
12+
export class JsSourceParser {
1613
/**
1714
* @param {object} options
1815
* @param {boolean} options.isEcmaScriptModule
1916
*/
20-
parseScript(options = {}) {
17+
parse(source, options = {}) {
2118
const {
2219
isEcmaScriptModule
2320
} = options;
2421

2522
try {
2623
const { body } = meriyah.parseScript(
27-
this.source,
24+
source,
2825
{
2926
...kParsingOptions,
3027
module: isEcmaScriptModule,
@@ -43,7 +40,7 @@ export class JsSourceParser extends SourceParser {
4340
isIllegalReturn
4441
)) {
4542
const { body } = meriyah.parseScript(
46-
this.source,
43+
source,
4744
{
4845
...kParsingOptions,
4946
module: true,
@@ -58,4 +55,3 @@ export class JsSourceParser extends SourceParser {
5855
}
5956
}
6057
}
61-

src/SourceParser.js

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)