2025-04-15 16:39:47 +02:00

332 lines
7.9 KiB
JavaScript

'use strict';
/**
* Create an instance of `Parser` with
* the given `string`, optionally passing
* a `parent` name for namespacing methods
*
* ```js
* const { Parser } = require('parse-code-context');
* const parser = new Parser('function foo(a, b, c) {}');
* ```
* @name Parser
* @param {String} `str`
* @param {String} `parent`
* @api public
*/
class Parser {
constructor(parent) {
this.parent = parent;
this.fns = [];
this.init();
}
/**
* Convenience method for creating a property name
* that is prefixed with the parent namespace, if defined.
*
* @name .name
* @param {String} `name`
* @return {String}
* @api public
*/
name(name) {
return this.parent ? this.parent + (name || '') : '';
}
/**
* Register a parser to use (in addition to those already
* registered as default parsers) with the given `regex` and
* function.
*
* ```js
* const parser = new Parser('function foo(a, b, c){}');
* .capture(/function\s*([\w$]+)\s*\(([^)]+)/, (match) => {
* return {
* name: match[1],
* params: matc(h[2] || '').split(/[,\s]/)
* };
* });
* ```
* @name .capture
* @param {RegExp} `regex`
* @param {Function} `fn`
* @return {Object} The instance for chaining
* @api public
*/
capture(regex, fn) {
this.fns.push({ regex, fn });
return this;
}
/**
* Parse the string passed to the constructor with all registered parsers.
*
* @name .parse
* @return {Object|Null}
* @api public
*/
parse(str, parent) {
this.parent = parent || this.parent;
for (let parser of this.fns) {
let re = parser.regex;
let fn = parser.fn;
let match = re.exec(str);
if (match) {
let ctx = fn.call(this, match, this.parent);
if (ctx) {
ctx.match = match;
this.value = ctx;
return ctx;
}
}
}
}
init() {
// module.exports method
this.capture(/^(module\.exports)\s*=\s*function\s*\(([^)]+)/, (m, parent) => {
return {
type: 'method',
receiver: m[1],
name: '',
params: params(m[2]),
string: m[1] + '.' + m[2] + '()'
};
});
this.capture(/^(module\.exports)\s*=\s*function\s([\w$]+)\s*\(([^)]+)/, (m, parent) => {
return {
type: 'function',
subtype: 'expression',
receiver: m[1],
name: m[2],
params: params(m[3]),
string: m[2] + '()'
};
});
// class, possibly exported by name or as a default
this.capture(/^\s*(export(\s+default)?\s+)?class\s+([\w$]+)(\s+extends\s+([\w$.]+(?:\(.*\))?))?\s*{/, (m, parent) => {
return {
type: 'class',
ctor: m[3],
name: m[3],
extends: m[5],
string: 'new ' + m[3] + '()'
};
});
// class constructor
this.capture(/^\s*constructor\s*\(([^)]+)/, (m, parent) => {
return {
type: 'constructor',
ctor: this.parent,
name: 'constructor',
params: params(m[4]),
string: this.name('.prototype.') + 'constructor()'
};
});
// class method
this.capture(/^\s*(static)?\s*(\*?)\s*(\[Symbol\.[^\]]+\]|[\w$]+|\[.*\])\s*\(([^)]*)/, (m, parent) => {
return {
type: 'method',
ctor: this.parent,
name: m[2] + m[3],
params: params(m[4]),
static: m[1] === 'static',
generator: m[2] === '*',
string: this.name(m[1] ? '.' : '.prototype.') + m[2] + m[3] + '()'
};
});
// named function statement, possibly exported by name or as a default
this.capture(/^\s*(export(\s+default)?\s+)?function\s+([\w$]+)\s*\(([^)]+)/, (m, parent) => {
return {
type: 'function',
subtype: 'statement',
name: m[3],
params: params(m[4]),
string: m[3] + '()'
};
});
// anonymous function expression exported as a default
this.capture(/^\s*export\s+default\s+function\s*\(([^)]+)/, (m, parent) => {
return {
type: 'function',
name: m[1], // undefined
params: params(m[4]),
string: m[1] + '()'
};
});
// function expression
this.capture(/^return\s+function(?:\s+([\w$]+))?\s*\(([^)]+)/, (m, parent) => {
return {
type: 'function',
subtype: 'expression',
name: m[1],
params: params(m[4]),
string: m[1] + '()'
};
});
// function expression
this.capture(/^\s*(?:const|let|var)\s+([\w$]+)\s*=\s*function\s*\(([^)]+)/, (m, parent) => {
return {
type: 'function',
subtype: 'expression',
name: m[1],
params: params(m[2]),
string: (m[1] || '') + '()'
};
});
// prototype method
this.capture(/^\s*([\w$.]+)\s*\.\s*prototype\s*\.\s*([\w$]+)\s*=\s*function\s*\(([^)]+)/, (m, parent) => {
return {
type: 'prototype method',
category: 'method',
ctor: m[1],
name: m[2],
params: params(m[3]),
string: m[1] + '.prototype.' + m[2] + '()'
};
});
// prototype property
this.capture(/^\s*([\w$.]+)\s*\.\s*prototype\s*\.\s*([\w$]+)\s*=\s*([^\n;]+)/, (m, parent) => {
return {
type: 'prototype property',
ctor: m[1],
name: m[2],
value: trim(m[3]),
string: m[1] + '.prototype.' + m[2]
};
});
// prototype property without assignment
this.capture(/^\s*([\w$]+)\s*\.\s*prototype\s*\.\s*([\w$]+)\s*/, (m, parent) => {
return {
type: 'prototype property',
ctor: m[1],
name: m[2],
string: m[1] + '.prototype.' + m[2]
};
});
// inline prototype
this.capture(/^\s*([\w$.]+)\s*\.\s*prototype\s*=\s*{/, (m, parent) => {
return {
type: 'prototype',
ctor: m[1],
name: m[1],
string: m[1] + '.prototype'
};
});
// Fat arrow function
this.capture(/^\s*\(*\s*([\w$.]+)\s*\)*\s*=>/, (m, parent) => {
return {
type: 'function',
ctor: this.parent,
name: m[1],
string: this.name('.prototype.') + m[1] + '()'
};
});
// inline method
this.capture(/^\s*([\w$.]+)\s*:\s*function\s*\(([^)]+)/, (m, parent) => {
return {
type: 'method',
ctor: this.parent,
name: m[1],
string: this.name('.prototype.') + m[1] + '()'
};
});
// inline property
this.capture(/^\s*([\w$.]+)\s*:\s*([^\n;]+)/, (m, parent) => {
return {
type: 'property',
ctor: this.parent,
name: m[1],
value: trim(m[2]),
string: this.name('.') + m[1]
};
});
// inline getter/setter
this.capture(/^\s*(get|set)\s*([\w$.]+)\s*\(([^)]+)/, (m, parent) => {
return {
type: 'property',
ctor: this.parent,
name: m[2],
string: this.name('.prototype.') + m[2]
};
});
// method
this.capture(/^\s*([\w$.]+)\s*\.\s*([\w$]+)\s*=\s*function\s*\(([^)]+)/, (m, parent) => {
return {
type: 'method',
receiver: m[1],
name: m[2],
params: params(m[3]),
string: m[1] + '.' + m[2] + '()'
};
});
// property
this.capture(/^\s*([\w$.]+)\s*\.\s*([\w$]+)\s*=\s*([^\n;]+)/, (m, parent) => {
return {
type: 'property',
receiver: m[1],
name: m[2],
value: trim(m[3]),
string: m[1] + '.' + m[2]
};
});
// declaration
this.capture(/^\s*(?:const|let|var)\s+([\w$]+)\s*=\s*([^\n;]+)/, (m, parent) => {
return {
type: 'declaration',
name: m[1],
value: trim(m[2]),
string: m[1]
};
});
}
}
function params(val) {
return trim(val).split(/[\s,]+/);
}
function trim(str) {
return toString(str).trim();
}
function toString(str) {
return str ? str.toString() : '';
}
/**
* Expose `parse`
*/
const parse = (str, options) => {
let parser = new Parser(options);
return parser.parse(str);
};
parse.Parser = Parser;
module.exports = parse;