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
estreeplugin - 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:
__anymatches any single node, but no extract__anlmatches array of nodes, but no extract__strmatches string literal, but no extract__arrmatches array of partial string literals, but no extract__any_aNamematches single node, return{aName: node}__anl_aNamematches array of nodes, return{aName: array_of_nodes}__str_aNamematches string literal, return{aName: value}__arr_aNamematches array, extract string literals, return{aName: [values]}
__arr, and__arr_aNamecan 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);