babel-helper-decorate-react
Version:
Babel Helper for custom decorator for React Component
194 lines (193 loc) • 7.52 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import * as types from '@babel/types';
import tpl from '@babel/template';
import { addDefault, addNamespace } from '@babel/helper-module-imports';
import parseCommentsRanges from './parseCommentsRanges';
import { isScopeDepthPassed } from './utils';
export class RangesHelper {
constructor(opts) {
this.opts = opts;
this.cache = new Map();
}
getLocation(path) {
var _a;
let loc = path.node.loc;
let tmp = path;
while (!loc && tmp) {
// @ts-ignore
tmp = tmp.getPrevSibling();
if (!(tmp === null || tmp === void 0 ? void 0 : tmp.node)) {
tmp = tmp.parentPath;
}
loc = (_a = tmp === null || tmp === void 0 ? void 0 : tmp.node) === null || _a === void 0 ? void 0 : _a.loc;
}
return loc;
}
eval(content) {
return `__$$EVAL%%${content}%%__`;
}
matched(path) {
return this.getEnableOptions(this.getLocation(path).start.line);
}
inject(path, transformData) {
let { matched, data } = this.matched(path);
if (!matched) {
return false;
}
if (!this.cache.get(path.node)) {
this.cache.set(path.node, []);
}
const decorated = this.cache.get(path.node);
if (decorated.includes(this.opts.libPath)) {
return path.skip();
}
decorated.push(this.opts.libPath);
let importName = this.importName;
if (!importName) {
const moduleInteropPath = this.opts.moduleInteropPath;
if (this.opts.importType === 'namespace') {
this.importName = addNamespace(path, this.opts.libPath, { nameHint: 'decorate' });
}
else {
this.importName = addDefault(path, this.opts.libPath, { nameHint: 'decorate' });
}
if (moduleInteropPath) {
const moduleInterop = addDefault(path, moduleInteropPath);
this.importName = types.callExpression(moduleInterop, [this.importName]);
}
importName = this.importName;
}
else {
importName = types.cloneDeep(this.importName);
}
// this.importName
if (transformData) {
data = transformData(data, path, this);
}
let dataExp = tpl.expression(JSON.stringify(data || null).replace(/"__\$\$EVAL%%(.*?)%%__"/g, '$1'))();
if ('ClassDeclaration' === path.node.type) {
path.node.decorators = path.node.decorators || [];
path.node.decorators.push(types.decorator(types.callExpression(importName, [dataExp])));
}
else {
// @ts-ignore
path.replaceWith(types.callExpression(types.callExpression(importName, [dataExp]), [path.node]));
}
}
getEnableOptions(line) {
for (const r of this.ranges) {
if (r.type === 'disable') {
if (r.has(line)) {
return {
matched: false,
data: null
};
}
}
if (r.has(line)) {
return {
matched: true,
data: r.data
};
}
}
return {
matched: this.opts.defaultEnable,
data: null
};
}
}
class WalkApi {
constructor() {
this.statusList = new Set();
}
addStatus(s) {
return this.statusList.add(s);
}
removeStatus(s) {
return this.statusList.delete(s);
}
hasStatus(s) {
return this.statusList.has(s);
}
noSkip(f = true) {
f ? this.addStatus('noSkip') : this.removeStatus('noSkip');
}
wrap(f = true) {
f ? this.addStatus('wrap') : this.removeStatus('wrap');
}
}
function createDecorateVisitor(_a = {}) {
var { prefix = 'decorate', decorateLibPath, moduleInteropPath = require.resolve('module-interop'), visitorTypes = ['FunctionExpression', 'ArrowFunctionExpression', 'ClassExpression', 'ClassDeclaration'], deepVisitorTypes = visitorTypes, exportVisitorTypes = ['ExportDefaultDeclaration', 'ExportNamedDeclaration'], defaultEnable = true, transformData, importType = 'default', detectScopeDepth = -1 } = _a, opts = __rest(_a, ["prefix", "decorateLibPath", "moduleInteropPath", "visitorTypes", "deepVisitorTypes", "exportVisitorTypes", "defaultEnable", "transformData", "importType", "detectScopeDepth"]);
if (!prefix) {
throw new Error('`prefix` is required');
}
if (!decorateLibPath) {
throw new Error('`decorateLibPath` is required');
}
const reduceVisitors = (types) => types.reduce((acc, name) => {
if (typeof name === 'string') {
acc[name] = function (path, { helper }) {
if (isScopeDepthPassed(path, detectScopeDepth)) {
helper.inject(path);
}
path.skip();
};
}
else {
acc[name.type] = function (path, { helper }) {
var _a;
const transform = name.transformData || transformData;
const walkApi = new WalkApi();
let rlt;
if (isScopeDepthPassed(path, detectScopeDepth)) {
if (!name.condition) {
helper.inject(path, (data) => (transform ? transform(data, path, helper.babelPass, helper) : data));
}
else if (((_a = helper.matched(path)) === null || _a === void 0 ? void 0 : _a.matched) &&
(rlt = name.condition(path, helper.babelPass, helper, walkApi)) &&
rlt === true) {
helper.inject(walkApi.hasStatus('wrap') ? path.parentPath : path, (data) => transform ? transform(data, path, helper.babelPass, helper) : data);
}
}
if (!walkApi.hasStatus('noSkip')) {
path.skip();
}
};
}
return acc;
}, {});
const deepVisitors = reduceVisitors(deepVisitorTypes);
const _visitors = reduceVisitors(visitorTypes);
let exportVisitors = exportVisitorTypes.reduce((acc, name) => {
acc[name] = function (path, state) {
path.traverse(deepVisitors, state);
path.skip();
};
return acc;
}, {});
return {
Program(path) {
const helper = new RangesHelper({
importType,
moduleInteropPath,
libPath: decorateLibPath,
defaultEnable
});
helper.babelPass = this;
helper.ranges = parseCommentsRanges(path.container.comments, Object.assign({ prefix }, opts));
path.traverse(Object.assign(Object.assign({}, _visitors), exportVisitors), { helper });
}
};
}
export default createDecorateVisitor;