Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/purple-emus-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nodesecure/estree-ast-utils": minor
---

Implement a new joinArrayExpression utility
33 changes: 32 additions & 1 deletion workspaces/estree-ast-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ You can provide a custom `externalIdentifierLookup` function to enable the utili
---

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

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

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

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

```ts
export interface ArrayExpressionToStringOptions extends DefaultOptions {
/**
* When enabled, resolves the char code of the literal value.
*
* @default true
* @example
* [65, 66] // => ['A', 'B']
*/
resolveCharCode?: boolean;
}
```

</details>

<details>
<summary>joinArrayExpression(node: ESTree.Node | null, options?: DefaultOptions): string | null</summary>

Compute simple ArrayExpression that are using a CallExpression `join()`

```js
{
host: [
["goo", "g", "gle"].join(""),
"com"
].join(".")
}
```

Will return `google.com`

</details>

<details>
Expand Down
126 changes: 126 additions & 0 deletions workspaces/estree-ast-utils/src/arrayExpression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Import Third-party Dependencies
import type { ESTree } from "meriyah";

// Import Internal Dependencies
import {
type DefaultOptions,
noop
} from "./options.js";
import {
isNode,
isCallExpression,
isLiteral
} from "./utils/is.js";
import {
getMemberExpressionIdentifier
} from "./getMemberExpressionIdentifier.js";

export interface ArrayExpressionToStringOptions extends DefaultOptions {
/**
* When enabled, resolves the char code of the literal value.
*
* @default true
* @example
* [65, 66] // => ['A', 'B']
*/
resolveCharCode?: boolean;
}

export function* arrayExpressionToString(
node: ESTree.Node | null,
options: ArrayExpressionToStringOptions = {}
): IterableIterator<string> {
const {
externalIdentifierLookup = noop,
resolveCharCode = true
} = options;

if (!isNode(node) || node.type !== "ArrayExpression") {
return;
}

for (const row of node.elements) {
if (row === null) {
continue;
}

switch (row.type) {
case "Literal": {
if (
row.value === ""
) {
continue;
}

if (resolveCharCode) {
const value = Number(row.value);
yield Number.isNaN(value) ?
String(row.value) :
String.fromCharCode(value);
}
else {
yield String(row.value);
}

break;
}
case "Identifier": {
const identifier = externalIdentifierLookup(row.name);
if (identifier !== null) {
yield identifier;
}
break;
}
case "CallExpression": {
const value = joinArrayExpression(row, {
externalIdentifierLookup
});
if (value !== null) {
yield value;
}
break;
}
}
}
}

export function joinArrayExpression(
node: ESTree.Node | null,
options: DefaultOptions = {}
): string | null {
if (!isCallExpression(node)) {
return null;
}

if (
node.arguments.length !== 1 ||
(
node.callee.type !== "MemberExpression" ||
node.callee.object.type !== "ArrayExpression"
)
) {
return null;
}

const id = Array.from(
getMemberExpressionIdentifier(node.callee)
).join(".");
if (
id !== "join" ||
!isLiteral(node.arguments[0])
) {
return null;
}

const separator = node.arguments[0].value;

const iter = arrayExpressionToString(
node.callee.object,
{
...options,
resolveCharCode: false
}
);

return [...iter].join(separator);
}
48 changes: 0 additions & 48 deletions workspaces/estree-ast-utils/src/arrayExpressionToString.ts

This file was deleted.

2 changes: 1 addition & 1 deletion workspaces/estree-ast-utils/src/concatBinaryExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { ESTree } from "meriyah";

// Import Internal Dependencies
import { arrayExpressionToString } from "./arrayExpressionToString.js";
import { arrayExpressionToString } from "./arrayExpression.js";
import {
type DefaultOptions,
noop
Expand Down
3 changes: 2 additions & 1 deletion workspaces/estree-ast-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from "./getCallExpressionIdentifier.js";
export * from "./getVariableDeclarationIdentifiers.js";
export * from "./getCallExpressionArguments.js";
export * from "./concatBinaryExpression.js";
export * from "./arrayExpressionToString.js";
export * from "./arrayExpression.js";
export * from "./extractLogicalExpression.js";
export * from "./utils/is.js";
export type { DefaultOptions } from "./options.js";
35 changes: 35 additions & 0 deletions workspaces/estree-ast-utils/src/utils/is.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Import Third-party Dependencies
import type { ESTree } from "meriyah";

export type Literal<T> = ESTree.Literal & {
value: T;
};

export type RegExpLiteral<T> = ESTree.RegExpLiteral & {
value: T;
};

export function isNode(
value: any
): value is ESTree.Node {
return (
value !== null &&
typeof value === "object" &&
"type" in value &&
typeof value.type === "string"
);
}

export function isCallExpression(
node: any
): node is ESTree.CallExpression {
return isNode(node) && node.type === "CallExpression";
}

export function isLiteral(
node: any
): node is Literal<string> {
return isNode(node) &&
node.type === "Literal" &&
typeof node.value === "string";
}
Loading