Code Monkey home page Code Monkey logo

magicast's Introduction

๐Ÿง€ Magicast

npm version npm downloads bundle Codecov License JSDocs

Programmatically modify JavaScript and TypeScript source codes with a simplified, elegant and familiar syntax. Built on top of the AST parsed by recast and babel.

โฏ ๐Ÿง™๐Ÿผ Magical modify a JS/TS file and write back magically just like JSON!
โฏ ๐Ÿ”€ Exports/Import manipulate module's imports and exports at ease
โฏ ๐Ÿ’ผ Function Arguments easily manipulate arguments passed to a function call, like defineConfig()
โฏ ๐ŸŽจ Smart Formatting preseves the formatting style (quotes, tabs, etc.) from the original code
โฏ ๐Ÿง‘โ€๐Ÿ’ป Readable get rid of the complexity of AST manipulation and make your code super readable

Install

Install npm package:

# using yarn
yarn add --dev magicast

# using npm
npm install -D magicast

# using pnpm
pnpm add -D magicast

Import utilities:

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

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

Examples

Example: Modify a file:

config.js:

export default {
  foo: ["a"],
};

Code to modify and append b to foo prop of defaultExport:

import { loadFile, writeFile } from "magicast";

const mod = await loadFile("config.js");

mod.exports.default.foo.push("b");

await writeFile(mod, "config.js");

Updated config.js:

export default {
  foo: ["a", "b"],
};

Example: Directly use AST utils:

import { parseModule, generateCode } from "magicast";

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

// Ensure foo is an array
mod.exports.default.foo ||= [];
// Add a new array member
mod.exports.default.foo.push("b");
mod.exports.default.foo.unshift("a");

// Generate code
const { code, map } = generateCode(mod);

Generated code:

export default {
  foo: ["a", "b"],
};

Example: Get the AST directly:

import { parseModule, generateCode } from "magicast";

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

const ast = mod.exports.default.$ast
// do something with ast

Example: Function arguments:

import { parseModule, generateCode } from "magicast";

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'
  ? mod.exports.default.$args[0]
  : mod.exports.default;

console.log(options.foo) // bar

Example: Create a function call:

import { parseModule, generateCode, builders } from "magicast";

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

const options = mod.exports.default.list = builders.functionCall('create', [1, 2, 3])

console.log(mod.generateCode()) // export default { list: create([1, 2, 3]) }

Notes

As JavaScript is a very dynamic language, you should be aware that Magicast's convention CAN NOT cover all possible cases. Magicast serves as a simple and maintainable interface to update static-ish JavaScript code. When interacting with Magicast node, be aware that every option might have chance to throw an error depending on the input code. We recommend to always wrap the code in a try/catch block (even better to do some defensive coding), for example:

import { loadFile, writeFile } from "magicast";

function updateConfig() {
  try {
    const mod = await loadFile("config.js");

    mod.exports.default.foo.push("b");

    await writeFile(mod);
  } catch (e) {
    console.error('Unable to update config.js')
    console.error('Please update it manually with the following instructions: ...')
    // handle error
  }
}

High Level Helpers

We also experiment to provide a few high level helpers to make common tasks easier. You could import them from magicast/helpers. They might be moved to a separate package in the future.

import {
  deepMergeObject,
  addNuxtModule,
  addVitePlugin,
  // ...
} from "magicast/helpers";

We recommend to check out the source code and test cases for more details.

Development

  • Clone this repository
  • Install latest LTS version of Node.js
  • Enable Corepack using corepack enable
  • Install dependencies using pnpm install
  • Run interactive tests using pnpm dev

License

Made with ๐Ÿ’›

Published under MIT License.

magicast's People

Contributors

andarist avatar antfu avatar ariperkkio avatar atinux avatar barbapapazes avatar betteroneday avatar hugoattal avatar igorbabko avatar larbish avatar lihbr avatar liuseen-l avatar lms24 avatar mannil avatar pi0 avatar pinkchampagne17 avatar renovate[bot] avatar samuelstroschein avatar tahul avatar zoeyzhao19 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

magicast's Issues

Support exported functions

Describe the feature

It would be great if parseModule would also find exported functions. For example:

export function foo() {
}

It currently exports exported constants, or the output of a function as the default. But it doesn't cover exported functions.

I personally don't require symmetric support on the generateCode side, which I get could be very challenging.

Additional information

  • Would you be willing to help implement this feature?

Export parseModule and generateCode in a different module that doesn't rely on node api

Describe the feature

Currently parseModule and generateCode are exported from the code module that import node:fs this made them impossible to use in a browser environment when actually they are not really dependent on node apis.

Maybe we can consider export them from another module that does not depends on node api to allow their usage even in the browser.

Additional information

  • Would you be willing to help implement this feature?

Best way to traverse nodes?

I'm new to the AST world and trying magicast to do some codemod stuff. Currently I'm using @babel/types#traverse function which I assumed was the same as @babel/traverse.

When I do this snippet below using @babel/types#traverse, things work as intended:

import * as fs from "node:fs/promises";
import * as path from "node:path";
import * as t from "@babel/types";
import * as m from "magicast";

const filename = path.join(process.cwd(), "src/client.ts");
const content = await fs.readFile(filename, { encoding: "utf-8" });
const mod = m.parseModule(content);

t.assertProgram(mod.$ast);
t.traverse(mod.$ast, {
  enter: (p) => {
    ...
  },
});

But when I swap the traverse method using @babel/traverse, program errors out and shows this error:

  import * as fs from "node:fs/promises";
  import * as path from "node:path";
- import * as t from "@babel/types";
+ import traverse from "@babel/traverse";
  import * as m from "magicast";

  const filename = path.join(process.cwd(), "src/client.ts");
  const content = await fs.readFile(filename, { encoding: "utf-8" });
  const mod = m.parseModule(content);

  t.assertProgram(mod.$ast);
- t.traverse(mod.$ast, {
+ traverse(mod.$ast, {
    enter: (p) => {
      ...
    },
  });
/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/scope/index.js:740
    throw new Error("Couldn't find a Program");
          ^


Error: Couldn't find a Program
    at Scope.getProgramParent (/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/scope/index.js:740:11)
    at Scope.crawl (/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/scope/index.js:663:32)
    at Scope.init (/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/scope/index.js:653:12)
    at NodePath.setScope (/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/path/context.js:116:30)
    at NodePath.setContext (/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/path/context.js:128:8)
    at NodePath.pushContext (/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/path/context.js:185:8)
    at TraversalContext.visitQueue (/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/context.js:78:14)
    at TraversalContext.visitSingle (/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/context.js:65:19)
    at TraversalContext.visit (/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/context.js:109:19)
    at traverseNode (/Users/grikomsn/Workspace/.../node_modules/.pnpm/@[email protected]/node_modules/@babel/traverse/lib/traverse-node.js:18:17)

Any ideas what I did wrong? What's the best way to traverse magicast's AST?

Support function declarations

Describe the feature

Maybe intentional, maybe not, but function declarations seem to be not supported:

const mod = parseModule(`export default function foo(){};`);
// Throws MagicastError: Casting "FunctionDeclaration" is not supported
console.log(mod.exports.default);

Additional information

  • Would you be willing to help implement this feature?

Compare magicast to alternatives

Describe the feature

There are alternative tools in this area, e.g. ts-morph. It would be awesome to know where magicast shines and where it is weaker, to know how to choose a tool better. Any clarification here or in the readme for the future readers would be appreciated!

Additional information

  • Would you be willing to help implement this feature?

Support satisfies keyword

Describe the feature

Looks like it throws an error when it encounters the satisfies keyword currently

can probably just ignore it for now would unblock me

Additional information

  • Would you be willing to help implement this feature?

`$comment` support

We could add this to all the proxy to be able to update the comments at any point of the ast. This way we could injecting some guidelines on modifying configs etc.

I will send the PR later today

`parseModule` does not include object properties with quotes in their names

Environment

$ node -v
v18.17.1

$ pnpm -v
8.7.5

$ cat package.json | grep magicast
    "magicast": "^0.3.1",

Reproduction

import { parseModule } from "magicast";

const mod = parseModule(`
export default defineConfig({
  test: {
    thisIsOk: {
      key: 111,
    },

    "thisIsBroken": {
      key: 222,
    },

    'thisIsAlsoBroken': {
      key: 333,
    },
  },
});
`);

const options =
  mod.exports.default.$type === "function-call"
    ? mod.exports.default.$args[0]
    : mod.exports.default;

console.log("test.thisIsOk", options.test.thisIsOk?.key); // โœ…
console.log("test.thisIsBroken", options.test.thisIsBroken?.key); // โŒ
console.log("test.thisIsAlsoBroken", options.test.thisIsAlsoBroken?.key); // โŒ

// Let's see if quotes are included in key names...
console.log("test.thisIsBroken#2", options.test['"thisIsBroken"']?.key); // โŒ
console.log("test.thisIsAlsoBroken#2", options.test["'thisIsAlsoBroken'"]?.key); // โŒ
test.thisIsOk 111
test.thisIsBroken undefined
test.thisIsAlsoBroken undefined
test.thisIsBroken#2 undefined
test.thisIsAlsoBroken#2 undefined

Describe the bug

It's not possible to access object properties that have quotes in their property names. Properties without quotes work fine.

In my use-case object properties can be glob-patterns so they need to be wrapped. Object keys like "**/utils/**" must be wrapped:

import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    coverage: {
      thresholds: {
        // Thresholds for all files
        lines: 100,
        functions: 100,
        branches: 80,
        statements: 90,

        // Thresholds for utilities
        "**/utils/**": {
          lines: 80,
          statements: 50,
        },
      },
    },
  },
});

Additional context

vitest-dev/vitest#4442

Logs

No response

Cannot be used in Node 14

Environment


  • Operating System: macOS
  • Node Version: v14.21.3

Reproduction

const { parseModule, generateCode } = require("magicast");

const mod = parseModule(`
class Test {
  test() {
    console.log('test')
  }
}
`);

// The following code runs well in Node 16 version, but throws an error in Node 14.
// โ€œTypeError: trimmitedLine.at is not a functionโ€
const { code } = generateCode(mod)

Describe the bug

When running the code in Node 14 version, an error occurs.

TypeError: trimmitedLine.at is not a function
at detectCodeFormat (/Users/zhangjunfeng9/Documents/Personal/demos/magicast-demo/node_modules/magicast/dist/index.cjs:8777:23)
at generateCode (/Users/zhangjunfeng9/Documents/Personal/demos/magicast-demo/node_modules/magicast/dist/index.cjs:8850:79)
at Object. (/Users/zhangjunfeng9/Documents/Personal/demos/magicast-demo/src/index.js:11:18)
at Module._compile (internal/modules/cjs/loader.js:1114:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1143:10)
at Module.load (internal/modules/cjs/loader.js:979:32)
at Function.Module._load (internal/modules/cjs/loader.js:819:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)
at internal/main/run_main_module.js:17:47

Additional context

No response

Logs

No response

A Magicast instance

Describe the feature

import { parseCode, generateCode } from "magicast";

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

mod.exports.default.foo ||= [];
mod.exports.default.foo.push("b");
mod.exports.default.foo.unshift("a");

const { code, map } = generateCode(mod)

Into:

import { Magicast } from "magicast";

const mc = new Magicast(`export default { }`);

mc.exports.default.foo ||= [];
mc.exports.default.foo.push("b");
mc.exports.default.foo.unshift("a");

const { code, map } = mc.generate()

It could be a high-level wrapper that provides easier usage, or we could directly replace the parseCode generateCode functions.

Additional information

  • Would you be willing to help implement this feature?

Tab style is not kept

Environment

[email protected]
[email protected]
[email protected]

Reproduction

import { parseModule, generateCode } from 'magicast'
import { addVitePlugin } from 'magicast/helpers'

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

export default defineConfig({
	plugins: []
})
`)

addVitePlugin(mod, {
	from: '@vitejs/plugin-vue',
	constructor: 'vue',
	options: {
		owo: true,
	},
})

const { code } = generateCode(mod)

console.log({ code })

Describe the bug

In the reproduction above, the indentation of plugins is a single tab. However, the generated code uses four tabs.

Additional context

Result:

{
  code: "import vue from '@vitejs/plugin-vue';\n" +
    "import { defineConfig } from 'vite'\n" +
    '\n' +
    'export default defineConfig({\n' +
    '\t\t\t\tplugins: [vue({\n' +
    '\t\t\t\t owo: true\n' +
    '\t\t\t\t})]\n' +
    '})'
}

Expected:

{
  code: "import vue from '@vitejs/plugin-vue';\n" +
    "import { defineConfig } from 'vite'\n" +
    '\n' +
    'export default defineConfig({\n' +
    '\tplugins: [vue({\n' +
    '\t\towo: true\n' +
    '\t})]\n' +
    '})'
}

Parsed module appears empty although it isn't

Environment

Hi, just came across this tool and I'd like to use it.

While playing around with it, I noticed something where I'm not sure if it is a bug or a feature:

The module object returned from parseModule appears to be empty when I log it to the console. However, it is not.

Reproduction

The example from the readme:

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

console.log(mod); // prints "{}"

mod.exports.default.foo ||= [];
mod.exports.default.foo.push("b");
mod.exports.default.foo.unshift("a");

const { code, map } = generateCode(mod);

console.log(code, map); // prints the expected result "export default {foo: ["a", "b"]};" 

Describe the bug

This makes it quite hard to understand what's going on and what I could possibly work with. For instance, I was wondering if I could check if a certain import was already made before adding the import. So the whole time, I was thinking that there's some problem in the parseModule function (or my input to it) when it was in fact working.

Maybe this is intentional (I noticed that mod is made up of a lot of proxies) but I wasn't aware of that at all.

Additional context

Please feel free to close this issue if this is intended. But I'd appreciate a small explanation :)

Logs

No response

Unable to get value using `NumericLiteral` or `BooleanLiteral` type keys

Environment

v18.17.0

Reproduction

const { generateCode, parseModule } = require("magicast");

const mod = parseModule(`
  export default {
    1: 2,
    [true]: 3,
    a: "b",
  };
`);
const obj = mod.exports.default;
Object.keys(obj).forEach((key) => (obj[key] = obj[key]));
console.log(generateCode(mod).code);

Describe the bug

Run the reproduction code, and then it will output:

export default {
  1: undefined,
  [true]: undefined,
  a: "b",
};

Expected result:

export default {
  1: 2,
  [true]: 3,
  a: "b",
};

Additional context

No response

Logs

No response

Is there a plan to support inserting declaration statements?

Describe the feature

expect

Insert a new statement before (or after) a statement.

import { parseModule, generateCode } from "magicast";

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

// Insert a declaration statement before the `export default` statement
mod.exports.before(`const a = "VariableDeclarator";`);

// Ensure foo is an array
mod.exports.default.foo ||= [];

// Add a variable as a new array member
mod.exports.default.foo.push("a", "var");

/**
 * const a = "VariableDeclarator";
 * export default {
 *   foo: [a],
 * };
 */
console.log(generateCode(mod).code);

Additional information

  • Would you be willing to help implement this feature?

Inner comment lost when manipulating an object

Environment

[email protected]

Reproduction

import { parseModule, generateCode } from "magicast";

const mod = parseModule(`

export default {
  /*inner comment*/
}

`);

mod.exports.default.foo ||= [];
mod.exports.default.foo.push("b");
mod.exports.default.foo.unshift("a");

const { code } = generateCode(mod);

console.log({ code });

Describe the bug

actual: an inner comment within an empty object is lost after manipulating that object
expected: the inner comment should be preserved

Additional context

No response

Logs

No response

Support quote vs double quote

I don't know how this is possible to know if use prefers to use quote vs double quotes?

After running nuxt module add nuxt-icon:

CleanShot 2023-02-17 at 10.55.20@2x.png

When add or modify imports, the format is not kept

Environment

Node: v18.15.0
magicast: v0.2.3

Reproduction

import { parseModule } from 'magicast'

const mod = parseModule(`import { defineConfig } from "foo"
export default defineConfig({})`)

mod.imports.defineConfig.from = 'bar'
console.log(mod.generate().code)

It outputs:

import { defineConfig } from "bar";
export default defineConfig({})

The semicolon should not be appended to the import declaration.

Describe the bug

When modify or add imports, the format is not kept.

Additional context

No response

Logs

No response

[Feature Request] Add `await` expressions or modify the `functionCall` builder to be able to be set to `await`'ed

Describe the feature

Everything seems to work just fine modifying modules that use await elsewhere as long as I don't touch await itself, if there's going to be significant complexity in adding a whole new proxied node for await, would it be easier just to make it part of the parts that handle function call expressions, like a flag parameter to prefix it?

I'm happy to help with it, though I'm new to doing AST stuff for the most part.

Additional information

  • Would you be willing to help implement this feature?

Named default export will cause error

Environment

magicast: 0.2.3

nodejs: v14.18.0

Reproduction

config.js

const data = {
};

export default data;

index.js

import {loadFile} from "magicast";

async function run() {
    const mod = await loadFile("./config.js");
    mod.exports.default.name = 'jim';
}

run();

Describe the bug

run index.js will show the error below:

UnhandledPromiseRejectionWarning: TypeError: 'set' on proxy: trap returned falsish for property 'name'

Cause analysis

image

As shown above, the code does not distinguish whether a declaration is an Identifier or other situations.

Additional context

No response

Logs

No response

Add support for file only imports

Describe the feature

Currently the way to add imports is using mod.imports.$add, which requires an ImportItemInput which requires an imported parameter, whereas it does not allow for generating such expression like
import 'my-global-styles.css'
On giving null or undefined as imported there is an error thrown

In my case it's particularly important for using AST to export custom css-in-jsx styles outside of file

image
image
image

I could take a look at it when pointed what would be the best way to modify it, and whether it's magicast or recast

Additional information

  • Would you be willing to help implement this feature?

deepMergeObject does not account for undefined property in recursion

Hi! ๐Ÿ‘‹

Firstly, thanks for your work on this project! ๐Ÿ™‚

Today I used patch-package to patch [email protected] for the project I'm working on.

When running

const requiredImports = `
import { browser } from "$app/environment"
import { initRootLayoutLoadWrapper } from "@inlang/sdk-js/adapter-sveltekit/shared"
import { initLocalStorageDetector, navigatorDetector } from "@inlang/sdk-js/detectors/client"
import { localStorageKey } from "@inlang/sdk-js/adapter-sveltekit/client/reactive"
`

const code = ''

const ast = parseModule(code)
const importsAst = parseModule(requiredImports)
deepMergeObject(ast, importsAst)

I get an error within deepMergeObject.
That's because the recursion happens to step into an undefined property.

Here is the diff that solved my problem:

diff --git a/node_modules/magicast/dist/helpers.cjs b/node_modules/magicast/dist/helpers.cjs
index 7813fd4..16d215a 100644
--- a/node_modules/magicast/dist/helpers.cjs
+++ b/node_modules/magicast/dist/helpers.cjs
@@ -8,7 +8,8 @@ require('@babel/parser');
 function deepMergeObject(magicast, object) {
   if (typeof object === "object") {
     for (const key in object) {
-      if (typeof object[key] === "object") {
+      if (typeof object[key] === "object" && magicast[key] !== undefined) {
+        // It could happen that magicast[key] is not defined
         deepMergeObject(magicast[key], object[key]);
       } else {
         magicast[key] = object[key];
diff --git a/node_modules/magicast/dist/helpers.mjs b/node_modules/magicast/dist/helpers.mjs
index 12c3234..fcb273c 100644
--- a/node_modules/magicast/dist/helpers.mjs
+++ b/node_modules/magicast/dist/helpers.mjs
@@ -6,7 +6,9 @@ import '@babel/parser';
 function deepMergeObject(magicast, object) {
   if (typeof object === "object") {
     for (const key in object) {
-      if (typeof object[key] === "object") {
+      if (typeof object[key] === "object" && magicast[key] !== undefined) {
+        // It could happen that magicast[key] is not defined
+        if(!magicast[key]) magicast[key] = {}
         deepMergeObject(magicast[key], object[key]);
       } else {
         magicast[key] = object[key];

This issue body was partially generated by patch-package.

Keys are not enumerable on `exports` and `imports`

Environment

magicast: 0.2.1
Node: v16.17.0

Reproduction

Here's a Codesandbox with an example.

This is the test snippet:

import { parseModule } from "magicast";

const mod = parseModule(`
import test from './foo'
export default defineConfig({ foo: 'bar' })
`);

console.log(mod.imports);
console.log(Object.keys(mod.imports));
console.log(mod.exports);
console.log(Object.keys(mod.exports));

Describe the bug

Both imports and exports are empty, even when the source string contains both.

Additional context

No response

Logs

No response

Open Source as MVP

Describe the feature

I am quite confident about the current state, and we could open it up and see how would the community usage be.

A few things left in my mind:

  • Better documentation
    • Bullet points of features
    • Better explanation
    • Remove warning?
  • Public interface #17

Additional information

  • Would you be willing to help implement this feature?

Parse and modify top-level declarations in a module

Describe the feature

I'd like to be able to modify top-level declarations within a module to, for example, modify objects that are exported in a separate statement. Something like:

// currently, there's no magicast-native way of accessing this declaration
const myConfig = defineConfig({/*...*/})
export default myConfig;

I'm using magicast in a script that's supposed to modify a user's vite.config.js and this quite common way of specifying a vite config unfortunately causes problems.

API proposal:

I'm thinking about something along these lines:

const originalCode = `
  const myConfig = defineConfig({/*...*/})
  export default myConfig;
`

const mod = parseModule(originalCode);

// example modification
mod.$declarations.myConfig = builders.raw(...)

Once we have a way of accessing/modifying declarations, we could for example adjust theaddVitePlugin helper to use the $declarations field to adjust the config object if it is not directly default-exported.

I'm happy to give this a try to implement but I'd like to hear your thoughts around this first.

This is somewhat related to #45 but I believe modifying/parsing existing declarations isn't discussed in this issue.

Limitations

I think we need to accept that we couldn't possibly handle all kinds of declarations or complex syntax within them but for starters, I believe handling object declarations or function calls (like defineConfig) might be enough.

Additional information

  • Would you be willing to help implement this feature?

Parse Re-exporting / Aggregating exports.

Environment

magicast: 0.2.3
Node: v16.17.0

Reproduction

Codesandbox.

import { parseModule } from "magicast";

const mod = parseModule(`
  export { default as function1, function2 } from "bar.js";
  export { default } from "foo.js";

  export { x } from "mod";
  export { x as v } from "mod";
  export * as ns from "mod";
`);

Describe the bug

Not sure if it's a bug or a feature request, but re-exporting files are not being "indexed" as neither imports nor exports. For reference, es-module-lexer parses them as imports.

I took the examples from MDN.

Additional context

No response

Logs

No response

Missing castings

Describe the feature

Hey ๐Ÿ™‚!

So I'd like to parse any code using builder.raw, but it fails on some castings.
For example, I'd like to generate foo(bar) (bar being a variable), doing it like this does not work:

builders.functionCall("foo", builders.raw("bar"));

Here are a few more examples.

const result = builders.raw("bar()");
console.log(generateCode(result)); // works
const result = builders.raw("bar"); // MagicastError: Casting "Identifier" is not supported
console.log(generateCode(result));
const result = builders.raw("() => 'a'"); // MagicastError: Casting "ArrowFunctionExpression" is not supported
console.log(generateCode(result));

I can help with the implementation if needed!

Additional information

  • Would you be willing to help implement this feature?

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • actions/checkout v4
  • actions/setup-node v4
  • codecov/codecov-action v4
npm
package.json
  • @babel/parser ^7.24.5
  • @babel/types ^7.24.5
  • source-map-js ^1.2.0
  • @types/node ^20.12.7
  • @vitest/coverage-v8 ^1.5.3
  • @vitest/ui ^1.5.3
  • ast-types ^0.16.1
  • changelogen ^0.5.5
  • eslint ^8.57.0
  • eslint-config-unjs ^0.2.1
  • esno ^4.7.0
  • giget ^1.2.3
  • lint-staged ^15.2.2
  • prettier ^3.2.5
  • recast ^0.23.6
  • simple-git-hooks ^2.11.1
  • typescript ^5.4.5
  • unbuild ^2.0.0
  • vitest ^1.5.3
  • pnpm 8.15.8

  • Check this box to trigger a request for Renovate to run again on this repository

`parseModule` and `parseExpression`

Currently our parseCode only expects module to be parsed. I think it could be useful to be able to also parse expressions, to update an object only etc. In that sense, we could rename parseCode to parseModule to be more explicit.

`useSemi: false` in format options ignored on imports

Environment

------------------------------
- Operating System: Windows_NT
- Node Version:     v20.10.0
- Package Manager:  [email protected]
------------------------------

Reproduction

https://github.com/vite-pwa/create-pwa :

  • pnpm install && nr build
  • cd examples && node ../index.js
  • follow the prompts (options doesn't matter) then open vite config file inside created folder

Describe the bug

useSemi: false shouldn't add imports with ;:

Generates import { VitePWA } from 'vite-plugin-pwa';

Additional context

No response

Logs

No response

[feature request] isolating/extracting modules

Describe the feature

Thanks for making magicast, I use it a lot ! There's something I would like to do :

Given this source file

export const A = "a"
export const B = "b"
export const C = "c"

I want to create 3 other files that look like this (ie I want to drop the other exports)

export const A = "a"
export const B = "b"
export const C = "c"

As far as I know the only way to do this would be to call loadFile 3 times, and to call delete file.exports[letterToDrop] 2 times for each output file.

Would it be possible to have an API that allows this :

const mod = await loadFile();
const a = isolate(mod.exports["A"])
const b = isolate(mod.exports["B"])
const c = isolate(mod.exports["C"])
generateCode(a) //export const A = "a"
generateCode(b) //export const B = "b"
generateCode(c) //export const C = "c"

We could also allow an array of exports :

const ab = isolate([mod.exports["A"], mod.exports["B"]])
generateCode(ab) //export const A = "a";export const B = "b";

Additional information

  • Would you be willing to help implement this feature?

Add builders.raw to use raw code

First of all, that's awesome what you guys are building ๐Ÿฅณ!


Describe the feature

I suggest being able to use builders with raw code.

Here's an example:

const ast = builders.functionCall("foo", "bar", 12, builders.raw("baz"));
const result = generateCode(result);
console.log(result.code) // 'foo("bar", 12, baz)'

Here, it's just to add variables in functionCall.

But the broader use case would be to be able to create any code, even if some builders function of magicast are missing.

For example, I would be able to do this

const ast = builders.raw("const foo = {bar:'baz'}");
const result = generateCode(result);
console.log(result.code) // 'const foo={bar:"bar"};'

While waiting to have builders to do it the "proper" way:

const ast = builders.assignment(
    "foo",
    builders.object([
        {
            key: "bar",
            value: builders.literal("baz")
        }
    ]),
    {
        const: true
    }
);
const result = generateCode(result);
console.log(result.code) // 'const foo={bar:"baz"};'

Wdyt?

Additional information

  • Would you be willing to help implement this feature?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.