Skip to content

Commit 53b25a4

Browse files
authored
feat(estree-ast-utils): add joinArrayExpression util (#377)
1 parent 3da3600 commit 53b25a4

File tree

10 files changed

+339
-130
lines changed

10 files changed

+339
-130
lines changed

.changeset/purple-emus-appear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nodesecure/estree-ast-utils": minor
3+
---
4+
5+
Implement a new joinArrayExpression utility

workspaces/estree-ast-utils/README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ You can provide a custom `externalIdentifierLookup` function to enable the utili
3333
---
3434

3535
<details>
36-
<summary>arrayExpressionToString(node: ESTree.Node | null): IterableIterator< string ></summary>
36+
<summary>arrayExpressionToString(node: ESTree.Node | null, options?: ArrayExpressionToStringOptions): IterableIterator< string ></summary>
3737

3838
Transforms an ESTree `ArrayExpression` into an iterable of literal values.
3939

@@ -43,6 +43,37 @@ Transforms an ESTree `ArrayExpression` into an iterable of literal values.
4343

4444
will yield `"foo"`, then `"bar"`.
4545

46+
```ts
47+
export interface ArrayExpressionToStringOptions extends DefaultOptions {
48+
/**
49+
* When enabled, resolves the char code of the literal value.
50+
*
51+
* @default true
52+
* @example
53+
* [65, 66] // => ['A', 'B']
54+
*/
55+
resolveCharCode?: boolean;
56+
}
57+
```
58+
59+
</details>
60+
61+
<details>
62+
<summary>joinArrayExpression(node: ESTree.Node | null, options?: DefaultOptions): string | null</summary>
63+
64+
Compute simple ArrayExpression that are using a CallExpression `join()`
65+
66+
```js
67+
{
68+
host: [
69+
["goo", "g", "gle"].join(""),
70+
"com"
71+
].join(".")
72+
}
73+
```
74+
75+
Will return `google.com`
76+
4677
</details>
4778

4879
<details>
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Import Third-party Dependencies
2+
import type { ESTree } from "meriyah";
3+
4+
// Import Internal Dependencies
5+
import {
6+
type DefaultOptions,
7+
noop
8+
} from "./options.js";
9+
import {
10+
isNode,
11+
isCallExpression,
12+
isLiteral
13+
} from "./utils/is.js";
14+
import {
15+
getMemberExpressionIdentifier
16+
} from "./getMemberExpressionIdentifier.js";
17+
18+
export interface ArrayExpressionToStringOptions extends DefaultOptions {
19+
/**
20+
* When enabled, resolves the char code of the literal value.
21+
*
22+
* @default true
23+
* @example
24+
* [65, 66] // => ['A', 'B']
25+
*/
26+
resolveCharCode?: boolean;
27+
}
28+
29+
export function* arrayExpressionToString(
30+
node: ESTree.Node | null,
31+
options: ArrayExpressionToStringOptions = {}
32+
): IterableIterator<string> {
33+
const {
34+
externalIdentifierLookup = noop,
35+
resolveCharCode = true
36+
} = options;
37+
38+
if (!isNode(node) || node.type !== "ArrayExpression") {
39+
return;
40+
}
41+
42+
for (const row of node.elements) {
43+
if (row === null) {
44+
continue;
45+
}
46+
47+
switch (row.type) {
48+
case "Literal": {
49+
if (
50+
row.value === ""
51+
) {
52+
continue;
53+
}
54+
55+
if (resolveCharCode) {
56+
const value = Number(row.value);
57+
yield Number.isNaN(value) ?
58+
String(row.value) :
59+
String.fromCharCode(value);
60+
}
61+
else {
62+
yield String(row.value);
63+
}
64+
65+
break;
66+
}
67+
case "Identifier": {
68+
const identifier = externalIdentifierLookup(row.name);
69+
if (identifier !== null) {
70+
yield identifier;
71+
}
72+
break;
73+
}
74+
case "CallExpression": {
75+
const value = joinArrayExpression(row, {
76+
externalIdentifierLookup
77+
});
78+
if (value !== null) {
79+
yield value;
80+
}
81+
break;
82+
}
83+
}
84+
}
85+
}
86+
87+
export function joinArrayExpression(
88+
node: ESTree.Node | null,
89+
options: DefaultOptions = {}
90+
): string | null {
91+
if (!isCallExpression(node)) {
92+
return null;
93+
}
94+
95+
if (
96+
node.arguments.length !== 1 ||
97+
(
98+
node.callee.type !== "MemberExpression" ||
99+
node.callee.object.type !== "ArrayExpression"
100+
)
101+
) {
102+
return null;
103+
}
104+
105+
const id = Array.from(
106+
getMemberExpressionIdentifier(node.callee)
107+
).join(".");
108+
if (
109+
id !== "join" ||
110+
!isLiteral(node.arguments[0])
111+
) {
112+
return null;
113+
}
114+
115+
const separator = node.arguments[0].value;
116+
117+
const iter = arrayExpressionToString(
118+
node.callee.object,
119+
{
120+
...options,
121+
resolveCharCode: false
122+
}
123+
);
124+
125+
return [...iter].join(separator);
126+
}

workspaces/estree-ast-utils/src/arrayExpressionToString.ts

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

workspaces/estree-ast-utils/src/concatBinaryExpression.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import type { ESTree } from "meriyah";
33

44
// Import Internal Dependencies
5-
import { arrayExpressionToString } from "./arrayExpressionToString.js";
5+
import { arrayExpressionToString } from "./arrayExpression.js";
66
import {
77
type DefaultOptions,
88
noop

workspaces/estree-ast-utils/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./getCallExpressionIdentifier.js";
33
export * from "./getVariableDeclarationIdentifiers.js";
44
export * from "./getCallExpressionArguments.js";
55
export * from "./concatBinaryExpression.js";
6-
export * from "./arrayExpressionToString.js";
6+
export * from "./arrayExpression.js";
77
export * from "./extractLogicalExpression.js";
8+
export * from "./utils/is.js";
89
export type { DefaultOptions } from "./options.js";
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Import Third-party Dependencies
2+
import type { ESTree } from "meriyah";
3+
4+
export type Literal<T> = ESTree.Literal & {
5+
value: T;
6+
};
7+
8+
export type RegExpLiteral<T> = ESTree.RegExpLiteral & {
9+
value: T;
10+
};
11+
12+
export function isNode(
13+
value: any
14+
): value is ESTree.Node {
15+
return (
16+
value !== null &&
17+
typeof value === "object" &&
18+
"type" in value &&
19+
typeof value.type === "string"
20+
);
21+
}
22+
23+
export function isCallExpression(
24+
node: any
25+
): node is ESTree.CallExpression {
26+
return isNode(node) && node.type === "CallExpression";
27+
}
28+
29+
export function isLiteral(
30+
node: any
31+
): node is Literal<string> {
32+
return isNode(node) &&
33+
node.type === "Literal" &&
34+
typeof node.value === "string";
35+
}

0 commit comments

Comments
 (0)