npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

gogoast

v1.0.4

Published

The simplest tool to parse/transform/generate code by ast

Downloads

21

Readme

GOGOAST

全网最简单易上手,可读性最强的AST处理工具!

Install

  • node包安装
    npm install gogoast
  • 浏览器执行

    commonjs引入gogoast.js

为什么你需要用gogoAST?

  • 大幅减少代码量——如果你需要使用AST对代码进行升级、改造、分析,快用gogoAST帮你摆脱繁琐冗余的的代码,专注于你的核心逻辑。不需要traverse,像剥洋葱一样一层一层的对比、操作、构造ast节点。
  • 降低理解成本——甚至不需要理解什么是CallExpression、Identifier、ImportDeclaration这些概念,就可以畅快运用AST。
  • 基于recast,转换后的代码基本与源代码的格式差异最小。
  • 凡是需要借助babel、recast、jscodeshift、esprima...完成的需求,gogoast都能帮你更快更简单的完成。

快速开始

  • 创建一个AST对象
const code = `some code`
const GG = require('gogoast');
const AST = GG.createAstObj(code);
  • 查找代码片段 用一段包含通配符($_$)的'代码选择器'来查找相应的代码片段,如:

    • 查找代码中所有的变量

      const { matchWildCardList } = AST.getAstsBySelector(`$_$`);
    • 查找代码中所有的字符串

      const { matchWildCardList } = AST.getAstsBySelector(`'$_$'`);
    • 查找某些函数的调用

      const { nodePathList, matchWildCardList } = AST.getAstsBySelector([
          '$_$.setTip($_$, $_$)',
          'tip.show($_$)'
      ]);
    • 选择器示例:

      var $_$ = $_$
      function $_$ () { $_$ }
      View.extend($_$)
      $_$ ? $_$ : $_$
      $_$ && $_$

返回结果 |nodePathList | matchWildCardList| -- |-- | :--: 描述|代码选择器匹配到的代码片段 | 代码选择器中通配符匹配到的代码片段 结构|nodePath[]|{ stucture: node, value: simpleNode }[] 解释|nodePath对象包含匹配到的ast结构及其上下文| structure中的node是ast结构,value是简化后的node,便于取值

  • 替换一段代码
  1. 在完整的AST片段中,通过param1查找对应的代码片段
  2. 通过param2生成一段代码,填入param1中通配符对应的代码
  3. 将param2生成的代码替换param1匹配的代码
    AST.replaceSelBySel(param1, param2);
    AST.replaceSelBySel('const $_$ = require($_$)', 'import $_$ from $_$');

gogoAST与主流AST工具之对比

目前,自定义babel插件和jscodeshift是有代表性的AST工具,通过下面两个例子可以看出gogoAST的代码可读性和简洁的优势。

  • 示例1: 与自定义babel插件对比

    对于下面这段js代码

    import a from 'a';
    console.log('get A')
    var b = console.log()
    console.log.bind()
    var c = console.log
    console.log = func

    假如我们希望对不同的console.log做不同的处理,变成下面这样

    import a from 'a';
    var b = void 0;
    console.log.bind()
    var c = function(){};
    console.log = func
    • 用自定义Babel插件实现的核心代码:
    // 代码来源:https://zhuanlan.zhihu.com/p/32189701
    module.exports = function({ types: t }) {
    return {
        name: "transform-remove-console",
        visitor: {
        CallExpression(path, state) {
            const callee = path.get("callee");
    
            if (!callee.isMemberExpression()) return;
    
            if (isIncludedConsole(callee, state.opts.exclude)) {
            // console.log()
            if (path.parentPath.isExpressionStatement()) {
                path.remove();
            } else {
            //var a = console.log()
                path.replaceWith(createVoid0());
            }
            } else if (isIncludedConsoleBind(callee, state.opts.exclude)) {
            // console.log.bind()
                path.replaceWith(createNoop());
            }
        },
        MemberExpression: {
            exit(path, state) {
            if (
                isIncludedConsole(path, state.opts.exclude) &&
                !path.parentPath.isMemberExpression()
            ) {
            //console.log = func
                if (
                path.parentPath.isAssignmentExpression() &&
                path.parentKey === "left"
                ) {
                path.parentPath.get("right").replaceWith(createNoop());
                } else {
                //var a = console.log
                path.replaceWith(createNoop());
                }
            }
            }
        }
        }
    };
    
    • 用gogoAST实现:
    const AST = GG.createAstObj(code);
    AST.replaceSelBySel(`var $_$ = console.log()`, `$_$ = void 0`);
    AST.replaceSelBySel(`console.log()`, null);
    AST.replaceSelBySel(`var $_$ = console.log`, `$_$ = function(){}`);
    const result = AST.generate();
  • 示例2: 与jscodeshift对比

    对于下面这段代码:

    import car from 'car';
    
    const suv = car.factory('white', 'Kia', 'Sorento', 2010, 50000, null, true);
    const truck = car.factory('silver', 'Toyota', 'Tacoma', 2006, 100000, true, true);

    假如我们希望将函数的多个入参封装为一个对象传入,变成下面这样

    import car from 'car';
    const suv = car.factory({"color":"white","make":"Kia","model":"Sorento","year":2010,"miles":50000,"bedliner":null,"alarm":true});
    const truck = car.factory({"color":"silver","make":"Toyota","model":"Tacoma","year":2006,"miles":100000,"bedliner":true,"alarm":true});
    • 用jscodeshift实现的核心代码:
    // 代码来源:https://www.toptal.com/javascript/write-code-to-rewrite-your-code
    export default (fileInfo, api) => {
    const j = api.jscodeshift;
    const root = j(fileInfo.source);
    
    // find declaration for "car" import
    const importDeclaration = root.find(j.ImportDeclaration, {
        source: {
        type: 'Literal',
        value: 'car',
        },
    });
    
    // get the local name for the imported module
    const localName =
        importDeclaration.find(j.Identifier)
        .get(0)
        .node.name;
    
    // current order of arguments
    const argKeys = [
        'color',
        'make',
        'model',
        'year',
        'miles',
        'bedliner',
        'alarm',
    ];
    
    // find where `.factory` is being called
    return root.find(j.CallExpression, {
        callee: {
            type: 'MemberExpression',
            object: {
            name: localName,
            },
            property: {
            name: 'factory',
            },
        }
        })
        .replaceWith(nodePath => {
        const { node } = nodePath;
    
        // use a builder to create the ObjectExpression
        const argumentsAsObject = j.objectExpression(
    
            // map the arguments to an Array of Property Nodes
            node.arguments.map((arg, i) =>
            j.property(
                'init',
                j.identifier(argKeys[i]),
                j.literal(arg.value)
            )
            )
        );
    
        // replace the arguments with our new ObjectExpression
        node.arguments = [argumentsAsObject];
    
        return node;
        })
    
        // specify print options for recast
        .toSource({ quote: 'single', trailingComma: true });
    };
    • 用gogoAST实现:
    const AST = GG.createAstObj(code);
    const argKeys = ['color', 'make', 'model', 'year', 'miles', 'bedliner', 'alarm' ];
    const { nodePathList, matchWildCardList } = AST.getAstsBySelector(`const $_$ = car.factory($_$)`, 'nn', false);
    nodePathList.forEach((path, i) => {
        const extra = matchWildCardList[i];
        const variableName = extra.shift().value;
        const obj = {};
        extra.forEach((ext, ei) => {
            obj[argKeys[ei]] = ext.structure.value
        });
        const newCall = GG.buildAstByAstStr(`${variableName} = car.factory(${JSON.stringify(obj)})`)
        AST.replaceAstByAst(path, newCall);
    })
    const result = AST.generate();

API

陆续补充中

  1. 创建一个实例:createAstObj

    const GG = require('gogoast');
    const AST = GG.createAstObj(p, options);    
    // options非必传,格式同babel-parse,如 { allowImportExportEverywhere: true, plugins: ['jsx'] }
  2. 通过选择器查找AST节点:getAstsBySelector

    • 选择器是一段包含通配符($_$)的代码
    • nodePathList:返回找到的ast节点路径,包含自己节点、父节点等信息
    • matchWildCardList:返回通配符$_$代表的节点信息,其中structure是节点完整信息,value是简略信息
    const { nodePathList, matchWildCardList } = AST.getAstsBySelector([
        '$_$.setTip($_$, $_$)',
        'tip.show($_$)'
    ]);
    
  3. 通过选择器替换另一个选择器查找到的AST节点:replaceSelBySel

    • 就像'abcd'.replace('a', 'z')一样好用
    AST.replaceSelBySel('const $_$ = require($_$)', 'import $_$ from $_$');
    AST.replaceSelBySel('$.extend(true, $_$, $_$)', 'Object.assign($_$, $_$)');
    AST.replaceSelBySel('$.each($_$, function($_$, $_$) { $_$ } )', '$_$.forEach($_$, $_$)');
  4. 创建一个AST节点:buildAstByAstStr

    • 可以通过字符串拼接AST节点的方式构造新的AST节点
    const type = 'error';
    const content = ASTNODE; // 从其他代码中提取出来或者自己构造的ast节点
    GG.buildAstByAstStr(`
        Alert.show({
            type: '${type}',
            content: '$_$content$_$'
        })
    `, {
        content
    })
  5. GG模块其他基本方法

    • 将AST节点转成字符串 generate(ast)
    • 获取AST节点的所有父节点 getParentListByAst(path)
    • 判断一个AST节点是否包含某子节点,子节点用选择器表示 hasChildrenSelector(path, childSelector)
    • 用一个AST节点替换某个字符串 replaceStrByAst
    • replaceAstByAst
    • getPrevAst
    • getNextAst
    • insertAstListBefore
    • insertAstListAfter
    • removeAst
    • ......
    • AST实例上也有部分方法,用法相比GG模块调用少传第一个参数,实际传入的是该实例自身的ast结构
  6. 特殊类型AST节点的构造方法

    • buildObjectProperty
    // 为什么不直接用buildAstByAstStr?把一个对象粘贴进astexplore就知道了
    AST.buildObjectProperty({
        url: 'getList',
        type: 'get'
    })
    • appendJsxAttr
    const locaid = '98s8dh3';
    const params = [ 'a=${aa}', 'b=${{aaa:"222",xxx:{ssss:111}}}', 'c=${"s"}', 'd=${a+1}','e=${ ss?2:1}' ]
    
    AST.appendJsxAttr({
       'attr1: `{${'`'}gostr='$'{gostr};locaid=d${locaid};${params.join('&')}}${'`'}}`, // 模板字符串
        a: `{a+1}`, // 表达式
        b: `'a'`, // 字符串
        c: `{a}` // 变量
    });
    
    // 结果:
    <div attr1={`gostr=${gostr};locaid=d98s8dh3;a=${aa}&b=${{aaa:"222",xxx{ssss:111}}}&c=${"s"}&d=${a+1}&e=${ ss?2:1}}`}a={a+1} b='a' c={a}>
    </div>

使用示例(陆续补充)

  1. 将一段代码最外层所有未export的变量定义都加上export
const GG = require('gogoast');

const AST = GG.createAstObj(code);     // code是源代码字符串

const { nodePathList } = AST.getAstsBySelector(`const $_$ = $_$`, true, 'n');   // 匹配到最外层的变量定义
nodePathList.forEach(p => {
    if (p.parent.node.type == 'ExportNamedDeclaration') {    // declarator类型的节点肯定至少存在两级parent,不会报错
        return;     // 已经export的不处理
    }
    GG.replaceAstByAst(p, { type: 'ExportNamedDeclaration', declaration: p.value })
})
console.log(AST.generate())     // 输出代码