Create pattern based AST matcher function. So you don't need to be an AST master in order to do static code analysis for JavaScript.
npm install ast-matcher
This tool can be used in Node.js
or browser. You may need some polyfill for missing JavaScript features in very old browsers.
Beware ast-matcher
doesn't install a parser for you. You need to manually install one.
We support any parser compatible with ESTree spec. Here are some popular ones:
- acorn
- @babel/parser with
estree
plugin - espree
- esprima
- meriyah
Take esprima
for example, you need to use setParser
to hook it up for ast-matcher
.
const esprima = require('esprima');
const astMatcher = require('ast-matcher');
// with es6, import astMatcher, { depFinder } from 'ast-matcher';
astMatcher.setParser(esprima.parse);
// or pass options to esprima
astMatcher.setParser(function(contents) {
return esprima.parse(contents, {jsx: true});
});
Beware @babel/parser
needs estree
plugin
const parser = require('@babel/parser');
const astMatcher = require('ast-matcher');
astMatcher.setParser(function(contents) {
return parser.parse(contents, {
// ... other options
plugins: [
// ... other plugins
'estree'
]
});
});
For TypeScript user, use:
import * as astMatcher from 'ast-matcher';`
let { depFinder } = astMatcher;
Pattern matching using AST on JavaScript source code.
const matcher = astMatcher('__any.method(__str_foo, [__arr_opts])')
matcher('au.method("a", ["b", "c"]); jq.method("d", ["e"])');
// => [
// {match: {foo: "a", opts: ["b", "c"]}, node: <CallExpression node> }
// {match: {foo: "d", opts: ["e"]}, node: <CallExpression node> }
// ]
astMatcher
takes a pattern
to be matched. The pattern can only be single statement, not multiple statements. Generates a function that:
- takes source code string (or estree syntax tree) as input,
- produces matched result or undefined.
Support following match terms in pattern:
__any
matches any single node, but no extract__anl
matches array of nodes, but no extract__str
matches string literal, but no extract__arr
matches array of partial string literals, but no extract__any_aName
matches single node, return{aName: node}
__anl_aName
matches array of nodes, return{aName: array_of_nodes}
__str_aName
matches string literal, return{aName: value}
__arr_aName
matches array, extract string literals, return{aName: [values]}
__arr
, and__arr_aName
can match partial string array.[foo, "foo", "bar", lorem] => ["foo", "bar"]
use
method(__anl)
ormethod(__arr_a)
to matchmethod(a, "b");
use
method([__anl])
ormethod([__arr_a])
to matchmethod([a, "b"]);
Dependency analysis for dummies, this is a high level api to simplify the usage of astMatcher
.
const depFinder = astMatcher.depFinder;
const finder = depFinder('a(__dep)', '__any.globalResources([__deps])');
finder('a("a"); a("b"); config.globalResources(["./c", "./d"])');
// => ['a', 'b', './c', './d']
depFinder
takes multiple patterns to match, instead of using __str_
/__arr
, use __dep
and __deps
to match string and partial string array. Generates a function that:
- takes source code string (or estree syntax tree) as input,
- produces an array of string matched, or empty array.
Beware AMD module could be wrapped commonjs module, you need to remove ['require', 'exports', 'module']
from the result.
const amdFind = depFinder(
'define([__deps], __any)', // anonymous module
'define(__str, [__deps], __any)' // named module
);
const deps = amdFind(amdJsFileContent_or_parsed_ast_tree);
const cjsFind = depFinder('require(__dep)');
const deps = cjsFind(cjsJsFileContent_or_parsed_ast_tree);
const matcher = astMatcher('if ( __any_condition ) { __anl_body }');
const m = matcher(code_or_parsed_ast_tree);
// => [
// {
// match: { condition: a_node, body: array_of_nodes },
// node: if_statement_node
// },
// ...
// ]
const matcher = astMatcher('if ( __any_condition ) { __anl_ifBody } else { __anl_elseBody }');
const m = matcher(code_or_parsed_ast_tree);
// => [
// {
// match: { condition: a_node, ifBody: array_of_nodes, elseBody: array_of_nodes },
// node: if_else_statement_node
// },
// ...
// ]
5. find Aurelia
framework's PLATFORM.moduleName()
dependencies
const auJsDepFinder = depFinder(
'PLATFORM.moduleName(__dep)',
'__any.PLATFORM.moduleName(__dep)',
'PLATFORM.moduleName(__dep, __any)',
'__any.PLATFORM.moduleName(__dep, __any)'
);
const deps = auJsDepFinder(auCode_or_parsed_ast_tree);