Merge branch 'API-2.0' into api2/for
This commit is contained in:
commit
ef4126b767
|
@ -17,15 +17,18 @@ import { PluginObj } from '@babel/core';
|
|||
import { Option } from './types';
|
||||
import * as babel from '@babel/core';
|
||||
import { PluginProvider } from './pluginProvider';
|
||||
import { ThisPatcher } from './thisPatcher';
|
||||
|
||||
export default function (api: typeof babel, options: Option): PluginObj {
|
||||
const pluginProvider = new PluginProvider(api, options);
|
||||
|
||||
const thisPatcher = new ThisPatcher(api);
|
||||
return {
|
||||
name: 'zouyu-2',
|
||||
visitor: {
|
||||
FunctionDeclaration(path) {
|
||||
pluginProvider.functionDeclarationVisitor(path);
|
||||
|
||||
thisPatcher.patch(path);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -89,25 +89,8 @@ class ClassComponentTransformer {
|
|||
private readonly t: typeof t;
|
||||
private readonly functionScope: Scope;
|
||||
|
||||
valueWrapper(node) {
|
||||
return this.t.file(this.t.program([this.t.isStatement(node) ? node : this.t.expressionStatement(node)]));
|
||||
}
|
||||
|
||||
addProperty(prop: t.ClassProperty | t.ClassMethod, name?: string) {
|
||||
this.properties.push(prop);
|
||||
if (name) {
|
||||
// replace the variable in scope to process the variable in class scope
|
||||
// e.g. replace () => count++ to () => this.count++
|
||||
// TODO: search for better solution
|
||||
this.functionScope.rename(name, `this.${name}`);
|
||||
this.functionScope.path.traverse({
|
||||
Identifier: path => {
|
||||
if (path.node.name === `this.${name}`) {
|
||||
path.replaceWith(this.t.memberExpression(this.t.thisExpression(), this.t.identifier(name)));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
constructor(babelApi: typeof babel, fnNode: NodePath<t.FunctionDeclaration>) {
|
||||
|
|
|
@ -204,15 +204,9 @@ describe('fn2Class', () => {
|
|||
transform(`
|
||||
export default function CountComp() {
|
||||
let count = 0;
|
||||
let countDown;
|
||||
let color
|
||||
for (let i = 0; i < count; i++) {
|
||||
console.log(\`The count change to: \${i}\`);
|
||||
}
|
||||
for (let i = 0; i < dbCount; i++) {
|
||||
console.log('color changed:', getColor());
|
||||
}
|
||||
function ()
|
||||
return <>
|
||||
<button onClick={() => count++}>Add</button>
|
||||
<div>{count}</div>
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
import { type types as t, NodePath } from '@babel/core';
|
||||
import * as babel from '@babel/core';
|
||||
|
||||
export class ThisPatcher {
|
||||
private readonly babelApi: typeof babel;
|
||||
private readonly t: typeof t;
|
||||
|
||||
private programNode: t.Program | undefined;
|
||||
|
||||
constructor(babelApi: typeof babel) {
|
||||
this.babelApi = babelApi;
|
||||
this.t = babelApi.types;
|
||||
}
|
||||
|
||||
patch = (classPath: NodePath<t.Class>) => {
|
||||
const classBodyNode = classPath.node.body;
|
||||
const availPropNames = classBodyNode.body
|
||||
.filter(
|
||||
(def): def is Exclude<t.ClassBody['body'][number], t.TSIndexSignature | t.StaticBlock> =>
|
||||
!this.t.isTSIndexSignature(def) && !this.t.isStaticBlock(def)
|
||||
)
|
||||
.map(def => ('name' in def.key ? def.key.name : null));
|
||||
|
||||
for (const memberOrMethod of classBodyNode.body) {
|
||||
classPath.scope.traverse(memberOrMethod, {
|
||||
Identifier: (path: NodePath<t.Identifier>) => {
|
||||
const idNode = path.node;
|
||||
if ('key' in memberOrMethod && idNode === memberOrMethod.key) return;
|
||||
const idName = idNode.name;
|
||||
if (
|
||||
availPropNames.includes(idName) &&
|
||||
!this.isMemberExpression(path) &&
|
||||
!this.isVariableDeclarator(path) &&
|
||||
!this.isAttrFromFunction(path, idName, memberOrMethod) &&
|
||||
!this.isObjectKey(path)
|
||||
) {
|
||||
path.replaceWith(this.t.memberExpression(this.t.thisExpression(), this.t.identifier(idName)));
|
||||
path.skip();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* check if the identifier is from a function param, e.g:
|
||||
* class MyClass {
|
||||
* ok = 1
|
||||
* myFunc1 = () => ok // change to myFunc1 = () => this.ok
|
||||
* myFunc2 = ok => ok // don't change !!!!
|
||||
* }
|
||||
*/
|
||||
isAttrFromFunction(path: NodePath<t.Identifier>, idName: string, stopNode: t.ClassBody['body'][number]) {
|
||||
let reversePath = path.parentPath;
|
||||
|
||||
const checkParam = (param: t.Node): boolean => {
|
||||
// ---- 3 general types:
|
||||
// * represent allow nesting
|
||||
// ---0 Identifier: (a)
|
||||
// ---1 RestElement: (...a) *
|
||||
// ---1 Pattern: 3 sub Pattern
|
||||
// -----0 AssignmentPattern: (a=1) *
|
||||
// -----1 ArrayPattern: ([a, b]) *
|
||||
// -----2 ObjectPattern: ({a, b})
|
||||
if (this.t.isIdentifier(param)) return param.name === idName;
|
||||
if (this.t.isAssignmentPattern(param)) return checkParam(param.left);
|
||||
if (this.t.isArrayPattern(param)) {
|
||||
return param.elements.map(el => checkParam(el)).includes(true);
|
||||
}
|
||||
if (this.t.isObjectPattern(param)) {
|
||||
return param.properties.map((prop: any) => prop.key.name).includes(idName);
|
||||
}
|
||||
if (this.t.isRestElement(param)) return checkParam(param.argument);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
while (reversePath && reversePath.node !== stopNode) {
|
||||
const node = reversePath.node;
|
||||
if (this.t.isArrowFunctionExpression(node) || this.t.isFunctionDeclaration(node)) {
|
||||
for (const param of node.params) {
|
||||
if (checkParam(param)) return true;
|
||||
}
|
||||
}
|
||||
reversePath = reversePath.parentPath;
|
||||
}
|
||||
if (this.t.isClassMethod(stopNode)) {
|
||||
for (const param of stopNode.params) {
|
||||
if (checkParam(param)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the identifier is already like `this.a` / `xx.a` but not like `a.xx` / xx[a]
|
||||
*/
|
||||
isMemberExpression(path: NodePath<t.Identifier>) {
|
||||
const parentNode = path.parentPath.node;
|
||||
return this.t.isMemberExpression(parentNode) && parentNode.property === path.node && !parentNode.computed;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the identifier is a variable declarator like `let a = 1` `for (let a in array)`
|
||||
*/
|
||||
isVariableDeclarator(path: NodePath<t.Identifier>) {
|
||||
const parentNode = path.parentPath.node;
|
||||
return this.t.isVariableDeclarator(parentNode) && parentNode.id === path.node;
|
||||
}
|
||||
|
||||
isObjectKey(path: NodePath<t.Identifier>) {
|
||||
const parentNode = path.parentPath.node;
|
||||
return this.t.isObjectProperty(parentNode) && parentNode.key === path.node;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue