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 { Option } from './types';
|
||||||
import * as babel from '@babel/core';
|
import * as babel from '@babel/core';
|
||||||
import { PluginProvider } from './pluginProvider';
|
import { PluginProvider } from './pluginProvider';
|
||||||
|
import { ThisPatcher } from './thisPatcher';
|
||||||
|
|
||||||
export default function (api: typeof babel, options: Option): PluginObj {
|
export default function (api: typeof babel, options: Option): PluginObj {
|
||||||
const pluginProvider = new PluginProvider(api, options);
|
const pluginProvider = new PluginProvider(api, options);
|
||||||
|
const thisPatcher = new ThisPatcher(api);
|
||||||
return {
|
return {
|
||||||
name: 'zouyu-2',
|
name: 'zouyu-2',
|
||||||
visitor: {
|
visitor: {
|
||||||
FunctionDeclaration(path) {
|
FunctionDeclaration(path) {
|
||||||
pluginProvider.functionDeclarationVisitor(path);
|
pluginProvider.functionDeclarationVisitor(path);
|
||||||
|
|
||||||
|
thisPatcher.patch(path);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -89,25 +89,8 @@ class ClassComponentTransformer {
|
||||||
private readonly t: typeof t;
|
private readonly t: typeof t;
|
||||||
private readonly functionScope: Scope;
|
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) {
|
addProperty(prop: t.ClassProperty | t.ClassMethod, name?: string) {
|
||||||
this.properties.push(prop);
|
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>) {
|
constructor(babelApi: typeof babel, fnNode: NodePath<t.FunctionDeclaration>) {
|
||||||
|
|
|
@ -204,15 +204,9 @@ describe('fn2Class', () => {
|
||||||
transform(`
|
transform(`
|
||||||
export default function CountComp() {
|
export default function CountComp() {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
let countDown;
|
|
||||||
let color
|
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
console.log(\`The count change to: \${i}\`);
|
console.log(\`The count change to: \${i}\`);
|
||||||
}
|
}
|
||||||
for (let i = 0; i < dbCount; i++) {
|
|
||||||
console.log('color changed:', getColor());
|
|
||||||
}
|
|
||||||
function ()
|
|
||||||
return <>
|
return <>
|
||||||
<button onClick={() => count++}>Add</button>
|
<button onClick={() => count++}>Add</button>
|
||||||
<div>{count}</div>
|
<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