Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: parseModule and parseExpression #24

Merged
merged 1 commit into from
Mar 3, 2023
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
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ Import utilities:

```js
// ESM / Bundler
import { parseCode, generateCode, builders, createNode } from "magicast";
import { parseModule, generateCode, builders, createNode } from "magicast";

// CommonJS
const { parseCode, generateCode, builders, createNode } = require("magicast");
const { parseModule, generateCode, builders, createNode } = require("magicast");
```

## Examples
Expand Down Expand Up @@ -73,10 +73,10 @@ export default {
**Example:** Directly use AST utils:

```js
import { parseCode, generateCode } from "magicast";
import { parseModule, generateCode } from "magicast";

// Parse to AST
const mod = parseCode(`export default { }`);
const mod = parseModule(`export default { }`);

// Ensure foo is an array
mod.exports.default.foo ||= [];
Expand All @@ -99,9 +99,9 @@ export default {
**Example:** Get the AST directly:

```js
import { parseCode, generateCode } from "magicast";
import { parseModule, generateCode } from "magicast";

const mod = parseCode(`export default { }`);
const mod = parseModule(`export default { }`);

const ast = mod.exports.default.$ast
// do something with ast
Expand All @@ -110,9 +110,9 @@ const ast = mod.exports.default.$ast
**Example:** Function parameters:

```js
import { parseCode, generateCode } from "magicast";
import { parseModule, generateCode } from "magicast";

const mod = parseCode(`export default defineConfig({ foo: 'bar' })`);
const mod = parseModule(`export default defineConfig({ foo: 'bar' })`);

// Support for both bare object export and `defineConfig` wrapper
const options = mod.exports.default.$type === 'function-call'
Expand Down
33 changes: 30 additions & 3 deletions src/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import {
ASTNode,
GenerateOptions,
ParsedFileNode,
Proxified,
ProxifiedModule,
} from "./types";
import { proxifyModule } from "./proxy/module";
import { detectCodeFormat } from "./format";
import { proxify } from "./proxy/proxify";

export function parseCode<Exports extends object = any>(
export function parseModule<Exports extends object = any>(
code: string,
options?: ParseOptions
): ProxifiedModule<Exports> {
Expand All @@ -21,11 +23,36 @@ export function parseCode<Exports extends object = any>(
return proxifyModule(node, code);
}

export function parseExpression<T>(
code: string,
options?: ParseOptions
): Proxified<T> {
const root: ParsedFileNode = parse("(" + code + ")", {
parser: options?.parser || getBabelParser(),
...options,
});
let body: ASTNode = root.program.body[0];
if (body.type === "ExpressionStatement") {
body = body.expression;
}
if (body.extra?.parenthesized) {
body.extra.parenthesized = false;
}

const mod = {
$ast: root,
$code: " " + code + " ",
$type: "module",
} as any as ProxifiedModule;

return proxify(body, mod);
}

export function generateCode(
node: { $ast: ASTNode } | ASTNode | ProxifiedModule<any>,
options: GenerateOptions = {}
): { code: string; map?: any } {
const ast = "$ast" in node ? node.$ast : node;
const ast = (node as Proxified).$ast || node;

const formatOptions =
options.format === false || !("$code" in node)
Expand All @@ -46,7 +73,7 @@ export async function loadFile<Exports extends object = any>(
): Promise<ProxifiedModule<Exports>> {
const contents = await fsp.readFile(filename, "utf8");
options.sourceFileName = options.sourceFileName ?? filename;
return parseCode(contents, options);
return parseModule(contents, options);
}

export async function writeFile(
Expand Down
6 changes: 3 additions & 3 deletions test/array.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";
import { parseCode } from "../src";
import { parseModule } from "../src";
import { generate } from "./_utils";

describe("array", () => {
it("array operations", () => {
const mod = parseCode<{ default: (number | any | string)[] }>(
const mod = parseModule<{ default: (number | any | string)[] }>(
`export default [1, 2, 3, 4, 5]`
);

Expand Down Expand Up @@ -43,7 +43,7 @@ describe("array", () => {
});

it("array should be iterable", () => {
const mod = parseCode(`
const mod = parseModule(`
export const config = {
array: ['a']
}
Expand Down
4 changes: 2 additions & 2 deletions test/builders.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { builders, parseCode } from "../src";
import { builders, parseModule } from "../src";
import { generate } from "./_utils";

describe("builders", () => {
Expand All @@ -19,7 +19,7 @@ describe("builders", () => {
]
`);

const mod = parseCode("");
const mod = parseModule("");
mod.exports.a = call;

expect(generate(mod)).toMatchInlineSnapshot(`
Expand Down
34 changes: 27 additions & 7 deletions test/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { expect, describe, it } from "vitest";
import { parseCode } from "../src";
import { parseExpression, parseModule } from "../src";
import { generate } from "./_utils";

describe("errors", () => {
it("ternary", () => {
const mod = parseCode(
const mod = parseModule(
`
export default {
a: 1 + 1 === 2
Expand All @@ -30,7 +30,7 @@ export default {
});

it("expression", () => {
const mod = parseCode(
const mod = parseModule(
`
export default {
a: 1 + 1
Expand All @@ -53,7 +53,7 @@ export default {

// TODO: This could be supported
it("identifier", () => {
const mod = parseCode(
const mod = parseModule(
`
const foo = {
bar: 1
Expand Down Expand Up @@ -81,7 +81,7 @@ export default {

// TODO: This could be supported
it("object shorthand", () => {
const mod = parseCode(
const mod = parseModule(
`
const foo = {
bar: 1
Expand All @@ -108,7 +108,7 @@ export default {
});

it("array destructuring", () => {
const mod = parseCode(
const mod = parseModule(
`
export default {
foo: [
Expand Down Expand Up @@ -147,7 +147,7 @@ export default {
});

it("object destructuring", () => {
const mod = parseCode(
const mod = parseModule(
`
export default {
foo: {
Expand Down Expand Up @@ -187,4 +187,24 @@ export default {
`
);
});

it("parseExpression", () => {
expect(() => parseExpression<any>("foo ? {} : []"))
.toThrowErrorMatchingInlineSnapshot(`
"Casting \\"ConditionalExpression\\" is not supported

1 | foo ? {} : []
^
"
`);

const exp = parseExpression<any>("{ a: foo ? {} : [] }");
expect(() => exp.a).toThrowErrorMatchingInlineSnapshot(`
"Casting \\"ConditionalExpression\\" is not supported

1 | { a: foo ? {} : [] }
^
"
`);
});
});
4 changes: 2 additions & 2 deletions test/exports.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { expect, it, describe } from "vitest";
import { parseCode } from "../src";
import { parseModule } from "../src";
import { generate } from "./_utils";

describe("exports", () => {
it("manipulate exports", () => {
const mod = parseCode("");
const mod = parseModule("");

expect(mod.exports).toMatchInlineSnapshot(`{}`);
expect(generate(mod)).toMatchInlineSnapshot('""');
Expand Down
8 changes: 4 additions & 4 deletions test/function-call.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";
import { builders, parseCode, ProxifiedModule } from "../src";
import { builders, parseModule, ProxifiedModule } from "../src";
import { generate } from "./_utils";

describe("function-calls", () => {
it("function wrapper", () => {
const mod = parseCode(`
const mod = parseModule(`
export const a: any = { foo: 1}
export default defineConfig({
// Modules
Expand Down Expand Up @@ -73,12 +73,12 @@ describe("function-calls", () => {
);
};

const mod1 = parseCode(`
const mod1 = parseModule(`
import { defineConfig } from 'vite'

export default defineConfig({})
`);
const mod2 = parseCode("");
const mod2 = parseModule("");

installVuePlugin(mod1);
installVuePlugin(mod2);
Expand Down
48 changes: 42 additions & 6 deletions test/general.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { expect, it, describe } from "vitest";
import { generateCode, parseCode } from "../src";
import { generateCode, parseModule, parseExpression } from "../src";
import { generate } from "./_utils";

describe("general", () => {
it("basic object and array", () => {
const mod = parseCode(`export default { a: 1, b: { c: {} } }`);
const mod = parseModule(`export default { a: 1, b: { c: {} } }`);

mod.exports.default.a = 2;

Expand Down Expand Up @@ -89,8 +89,8 @@ describe("general", () => {
});

it("mix two configs", () => {
const mod1 = parseCode(`export default { a: 1 }`);
const mod2 = parseCode(`export default { b: 2 }`);
const mod1 = parseModule(`export default { a: 1 }`);
const mod2 = parseModule(`export default { b: 2 }`);

mod1.exports.default.b = mod2.exports.default;

Expand All @@ -108,7 +108,7 @@ describe("general", () => {
});

it("delete property", () => {
const mod = parseCode(`export default { a: 1, b: [1, { foo: 'bar' }] }`);
const mod = parseModule(`export default { a: 1, b: [1, { foo: 'bar' }] }`);

delete mod.exports.default.b[1].foo;

Expand All @@ -130,7 +130,7 @@ describe("general", () => {
});

it("should preserve code styles", () => {
const mod = parseCode(`
const mod = parseModule(`
export const config = {
array: ['a']
}
Expand All @@ -142,4 +142,40 @@ describe("general", () => {
}"
`);
});

describe("parseExpression", () => {
it("object", () => {
const exp = parseExpression<any>("{ a: 1, b: 2 }");

expect(exp).toEqual({ a: 1, b: 2 });

exp.a = [1, 3, 4];

expect(generateCode(exp).code).toMatchInlineSnapshot(`
"{
a: [1, 3, 4],
b: 2
}"
`);
});

it("array", () => {
const exp = parseExpression<any>("[1, { foo: 2 }]");

expect(exp).toMatchInlineSnapshot(`
[
1,
{
"foo": 2,
},
]
`);

exp[2] = "foo";

expect(generateCode(exp).code).toMatchInlineSnapshot(
'"[1, { foo: 2 }, \\"foo\\"]"'
);
});
});
});
4 changes: 2 additions & 2 deletions test/imports.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { expect, it, describe } from "vitest";
import { parseCode } from "../src";
import { parseModule } from "../src";
import { generate } from "./_utils";

describe("imports", () => {
it("manipulate imports", () => {
const mod = parseCode(`
const mod = parseModule(`
import { defineConfig, Plugin } from 'vite'
import Vue from '@vitejs/plugin-vue'
import * as path from 'path'
Expand Down
4 changes: 2 additions & 2 deletions test/object.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";
import { parseCode } from "../src";
import { parseModule } from "../src";
import { generate } from "./_utils";

describe("object", () => {
it("object property", () => {
const mod = parseCode(
const mod = parseModule(
`
export default {
foo: {
Expand Down
Loading