Skip to content

Commit 3c2d35a

Browse files
authored
refactor(js-x-ray)!: migrate to TypeScript (#364)
* chore: migrate to TypeScript * chore(package.json): remove unused glob dependency * docs: update APIs
1 parent e37384c commit 3c2d35a

File tree

101 files changed

+1778
-1229
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+1778
-1229
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
"@openally/config.typescript": "^1.0.3",
2525
"@types/node": "^24.0.2",
2626
"c8": "^10.1.2",
27-
"glob": "^11.0.0",
2827
"iterator-matcher": "^2.1.0",
2928
"tsx": "^4.20.3",
3029
"typescript": "^5.8.3"

tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"files": [],
33
"references": [
4+
{
5+
"path": "./workspaces/js-x-ray"
6+
},
47
{
58
"path": "./workspaces/sec-literal"
69
},

workspaces/js-x-ray/docs/AstAnalyser.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ interface AstAnalyserOptions {
3131
/**
3232
* @default false
3333
*/
34-
optionalWarnings?: boolean | Iterable<string>;
34+
optionalWarnings?: boolean | Iterable<OptionalWarningName>;
3535
}
3636
```
3737

@@ -47,13 +47,14 @@ class AstAnalyser {
4747
options?: RuntimeOptions
4848
) => Report;
4949
analyseFile(
50-
pathToFile: string,
50+
pathToFile: string | URL,
5151
options?: RuntimeFileOptions
5252
): Promise<ReportOnFile>;
5353
analyseFileSync(
54-
pathToFile: string,
54+
pathToFile: string | URL,
5555
options?: RuntimeFileOptions
5656
): ReportOnFile;
57+
prepareSource(source: string, options?: PrepareSourceOptions): string
5758
}
5859
```
5960

@@ -148,7 +149,14 @@ export const customProbes = [
148149
main: (node, options) => {
149150
const { sourceFile, data: calleeName } = options;
150151
if (node.declarations[0].init.value === "danger") {
151-
sourceFile.addWarning("unsafe-danger", calleeName, node.loc);
152+
sourceFile.warnings.push({
153+
kind: "unsafe-danger",
154+
value: calleeName,
155+
location: node.loc,
156+
source: "JS-X-Ray Custom Probe",
157+
i18n: "sast_warnings.unsafe-danger",
158+
severity: "Warning"
159+
});
152160

153161
return ProbeSignals.Skip;
154162
}

workspaces/js-x-ray/package.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,22 @@
55
"type": "module",
66
"exports": {
77
".": {
8-
"import": "./src/index.js",
9-
"types": "./src/index.d.ts"
8+
"import": "./dist/index.js",
9+
"types": "./dist/index.d.ts"
1010
},
1111
"./warnings": {
12-
"import": "./src/warnings.js",
13-
"types": "./types/warnings.d.ts"
12+
"import": "./dist/warnings.js",
13+
"types": "./dist/warnings.d.ts"
1414
},
1515
"./package.json": "./package.json"
1616
},
1717
"engines": {
1818
"node": ">=20.0.0"
1919
},
2020
"scripts": {
21-
"test-only": "glob -c \"node --test-reporter=spec --test\" \"./test/**/*.spec.js\"",
21+
"prepublishOnly": "npm run build",
22+
"build": "tsc",
23+
"test-only": "tsx --test-reporter=spec --test \"./test/**/*.spec.ts\"",
2224
"test": "c8 --all --src ./src -r html npm run test-only"
2325
},
2426
"repository": {

workspaces/js-x-ray/src/AstAnalyser.js renamed to workspaces/js-x-ray/src/AstAnalyser.ts

Lines changed: 92 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,80 @@ import path from "node:path";
55

66
// Import Third-party Dependencies
77
import { walk } from "estree-walker";
8+
import type { ESTree } from "meriyah";
89
import isMinified from "is-minified-code";
910

1011
// Import Internal Dependencies
11-
import { SourceFile } from "./SourceFile.js";
12+
import { generateWarning, type Warning } from "./warnings.js";
13+
import {
14+
SourceFile,
15+
type ProbesOptions,
16+
type SourceFlags
17+
} from "./SourceFile.js";
1218
import { isOneLineExpressionExport } from "./utils/index.js";
13-
import { JsSourceParser } from "./JsSourceParser.js";
19+
import { JsSourceParser, type SourceParser } from "./JsSourceParser.js";
1420

15-
export class AstAnalyser {
21+
export interface Dependency {
22+
unsafe: boolean;
23+
inTry: boolean;
24+
location?: null | ESTree.SourceLocation;
25+
}
26+
27+
export interface RuntimeOptions {
28+
/**
29+
* @default true
30+
*/
31+
module?: boolean;
32+
/**
33+
* @default false
34+
*/
35+
removeHTMLComments?: boolean;
36+
/**
37+
* @default false
38+
*/
39+
isMinified?: boolean;
40+
initialize?: (sourceFile: SourceFile) => void;
41+
finalize?: (sourceFile: SourceFile) => void;
42+
}
43+
44+
export interface RuntimeFileOptions extends Omit<RuntimeOptions, "isMinified"> {
45+
packageName?: string;
46+
}
47+
48+
export interface Report {
49+
dependencies: Map<string, Dependency>;
50+
warnings: Warning[];
51+
flags: Set<SourceFlags>;
52+
idsLengthAvg: number;
53+
stringScore: number;
54+
}
55+
56+
export type ReportOnFile = {
57+
ok: true;
58+
warnings: Warning[];
59+
dependencies: Map<string, Dependency>;
60+
flags: Set<SourceFlags>;
61+
} | {
62+
ok: false;
63+
warnings: Warning[];
64+
};
65+
66+
export interface AstAnalyserOptions extends ProbesOptions {
1667
/**
17-
* @constructor
18-
* @param {object} [options={}]
19-
* @param {SourceParser} [options.customParser]
20-
* @param {Array<object>} [options.customProbes]
21-
* @param {boolean} [options.skipDefaultProbes=false]
22-
* @param {boolean | Iterable<string>} [options.optionalWarnings=false]
68+
* @default JsSourceParser
2369
*/
24-
constructor(options = {}) {
70+
customParser?: SourceParser;
71+
}
72+
73+
export interface PrepareSourceOptions {
74+
removeHTMLComments?: boolean;
75+
}
76+
77+
export class AstAnalyser {
78+
parser: SourceParser;
79+
probesOptions: ProbesOptions;
80+
81+
constructor(options: AstAnalyserOptions = {}) {
2582
this.parser = options.customParser ?? new JsSourceParser();
2683
this.probesOptions = {
2784
customProbes: options.customProbes ?? [],
@@ -30,7 +87,10 @@ export class AstAnalyser {
3087
};
3188
}
3289

33-
analyse(str, options = Object.create(null)) {
90+
analyse(
91+
str: string,
92+
options: RuntimeOptions = {}
93+
): Report {
3494
const {
3595
isMinified = false,
3696
module = true,
@@ -54,14 +114,15 @@ export class AstAnalyser {
54114
}
55115

56116
// we walk each AST Nodes, this is a purely synchronous I/O
117+
// @ts-expect-error
57118
walk(body, {
58119
enter(node) {
59120
// Skip the root of the AST.
60121
if (Array.isArray(node)) {
61122
return;
62123
}
63124

64-
const action = source.walk(node);
125+
const action = source.walk(node as ESTree.Node);
65126
if (action === "skip") {
66127
this.skip();
67128
}
@@ -90,9 +151,9 @@ export class AstAnalyser {
90151
}
91152

92153
async analyseFile(
93-
pathToFile,
94-
options = {}
95-
) {
154+
pathToFile: string | URL,
155+
options: RuntimeFileOptions = {}
156+
): Promise<ReportOnFile> {
96157
try {
97158
const {
98159
packageName = null,
@@ -130,20 +191,22 @@ export class AstAnalyser {
130191
flags: data.flags
131192
};
132193
}
133-
catch (error) {
194+
catch (error: any) {
134195
return {
135196
ok: false,
136197
warnings: [
137-
{ kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
198+
generateWarning("parsing-error", {
199+
value: error.message
200+
})
138201
]
139202
};
140203
}
141204
}
142205

143206
analyseFileSync(
144-
pathToFile,
145-
options = {}
146-
) {
207+
pathToFile: string | URL,
208+
options: RuntimeFileOptions = {}
209+
): ReportOnFile {
147210
try {
148211
const {
149212
packageName = null,
@@ -181,22 +244,22 @@ export class AstAnalyser {
181244
flags: data.flags
182245
};
183246
}
184-
catch (error) {
247+
catch (error: any) {
185248
return {
186249
ok: false,
187250
warnings: [
188-
{ kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
251+
generateWarning("parsing-error", {
252+
value: error.message
253+
})
189254
]
190255
};
191256
}
192257
}
193258

194-
/**
195-
* @param {!string} source
196-
* @param {object} options
197-
* @param {boolean} [options.removeHTMLComments=false]
198-
*/
199-
prepareSource(source, options = {}) {
259+
prepareSource(
260+
source: string,
261+
options: PrepareSourceOptions = {}
262+
): string {
200263
if (typeof source !== "string") {
201264
throw new TypeError("source must be a string");
202265
}
@@ -214,11 +277,7 @@ export class AstAnalyser {
214277
this.#removeHTMLComment(rawNoShebang) : rawNoShebang;
215278
}
216279

217-
/**
218-
* @param {!string} str
219-
* @returns {string}
220-
*/
221-
#removeHTMLComment(str) {
280+
#removeHTMLComment(str: string): string {
222281
return str.replaceAll(/<!--[\s\S]*?(?:-->)/g, "");
223282
}
224283
}

0 commit comments

Comments
 (0)