假设我有这个课程:
abstract class DynamicallyAccessible{
operator [](String key){
throw 'DynamicallyAcessible [] operator is not implemented';
}
operator []=(String key,Object value){
throw 'DynamicallyAcessible []= operator is not implemented';
}
call(String methodName,List<Object> params){
throw 'DynamicallyAcessible call method is not implemented';
}
}
以及扩展上述内容的这个类:
class Model extends DynamicallyAccessible{
String _a;
String get a => _a;
set a(v)=> _a=v;
String _b;
String get b => _b;
set b(v)=> _b=v;
int c;
dummyMethod(int a,int b){
return 6+a+b;
}
//this is to be generated by the transformer
//calls a getter or returns a value field by name
operator [](String key){
switch (key){
case 'a':return a;
case 'b':return b;
case 'c':return c;
default:throw 'no getter or field called $key';
}
}
//calls a setter or sets a field by name
operator []=(String key,Object value){
switch (key){
case 'a':a=value;return;
case 'b':b=value;return;
case 'c':c=value;return;
default:throw 'no setter or field called $key';
}
}
//calls a method by name
call(String key,List<Object> params){
switch(key){
case 'dummyMethod':return dummyMethod(params[0],params[1]);
default : throw 'no method called $key';
}
}
}
我手动实现这些方法是浪费时间,我的问题是是否存在类似存在的变换器,或者我必须从头开始编写一个,如果没有任何建议可以做得更好吗?
原因是避免使用带有dart2js的镜像
答案 0 :(得分:3)
以下是使用分析器包查找扩展DynamicallyAccessible并为操作符[]注入代码的类的转换器示例。
import 'package:barback/barback.dart';
import 'package:analyzer/analyzer.dart';
class DynamicallyAccessibleTransformer extends Transformer {
DynamicallyAccessibleTransformer.asPlugin();
get allowedExtensions => '.dart';
apply(Transform transform) async {
var content = await transform.readInputAsString(transform.primaryInput.id);
var newContent = _transformDartFile(content);
transform.addOutput(new Asset.fromString(transform.primaryInput.id, newContent));
}
String _transformDartFile(String content) {
CompilationUnit unit = parseCompilationUnit(content);
String newContent = content;
for (ClassDeclaration declaration in unit.declarations.reversed.where((d) => d is ClassDeclaration)) {
if (_isDynamicallyAccessible(declaration)) {
String sourceToInject = _createSourceToInject(declaration);
String before = content.substring(0, declaration.endToken.offset);
String after = content.substring(declaration.endToken.offset);
newContent = before + "\n$sourceToInject\n" + after;
}
}
return newContent;
}
/// TODO: this is a fragile approach as we only check for the class name
/// and not the library from where it comes. We probably should resolve
/// the Element and check the library.
_isDynamicallyAccessible(ClassDeclaration declaration) {
ExtendsClause extendsClause = declaration.extendsClause;
if (extendsClause != null) {
return extendsClause.superclass.name.name == 'DynamicallyAccessible';
}
return false;
}
_createSourceToInject(ClassDeclaration declaration) {
//TODO: do the same things for setters, methods and variable declaration.
String getterCases = declaration.members
.where((member) => member is MethodDeclaration && member.isGetter)
.map((MethodDeclaration getter) => getter.name.name)
.map((String name) => " case '$name': return $name;")
.join('\n');
return '''
operator [](String key) {
switch (key) {
$getterCases
default:throw 'no getter called \$key';
}
}
''';
}
}
答案 1 :(得分:1)
这是我的版本,也支持调用方法的位置和命名参数尚未经过深度测试但工作正常
//TODO:support multiple inheritance where the sub class gets access to the super class members
class DynamicTransformer extends Transformer {
DynamicTransformer.asPlugin();
String get allowedExtensions => ".dart";
Future apply(Transform transform) async{
var logger = new BuildLogger(transform);
var content = await transform.primaryInput.readAsString();
var sourceFile = new SourceFile(content);
var transaction = _transformCompilationUnit(content,sourceFile,logger);
var id = transform.primaryInput.id;
if(transaction.hasEdits){
var printer = transaction.commit();
var url = id.path.startsWith('lib/')
? 'package:${id.package}/${id.path.substring(4)}' : id.path;
printer.build(url);
transform.addOutput(new Asset.fromString(id,printer.text));
}
return logger.writeOutput();
}
Future<bool> isPrimary(id) {
return new Future.value(id.extension == '.dart');
}
TextEditTransaction _transformCompilationUnit(
String inputCode, SourceFile sourceFile, BuildLogger logger) {
var unit = parseCompilationUnit(inputCode, suppressErrors: true);
var editor = new TextEditTransaction(inputCode, sourceFile);
unit.declarations
.where((dec) => dec is ClassDeclaration &&
isSubClassOrHasMixinOf(dec, 'DynamicAccess'))
.forEach((classDec) {
_transformClass(classDec,editor,sourceFile,logger);
});
return editor;
}
void _transformClass(ClassDeclaration cls, TextEditTransaction editor,
SourceFile file, BuildLogger logger) {
//TODO:throw another exception for private fields
var readWriteFieldNames = getClassDeclarationFieldDeclarationNames(cls,public:true);
var readOnlyFieldNames = getClassDeclarationFieldDeclarationNames(cls,public:true,readOnly:true);
var getters = getClassDeclarationMethodDeclarationNames(cls,getter:true).union(readWriteFieldNames).union(readOnlyFieldNames);
var setters = getClassDeclarationMethodDeclarationNames(cls,setter:true).union(readWriteFieldNames);
var methods = getClassDeclarationMethodDeclarations(cls,normal:true);
var methodsString = _buildSquareBracketsOperatorGet(getters);
methodsString+=_buildSquareBracketsOperatorAssignment(setters,readOnlyFieldNames);
methodsString+=_buildCallMethod(methods);
editor.edit(cls.endToken.offset,cls.endToken.offset,'$methodsString');
}
//build [] operator method
String _buildSquareBracketsOperatorGet(Set<String> getters) {
var sb = new StringBuffer();
sb.writeln('operator [](String key){');
sb.writeln('switch (key){');
getters.forEach((getter) {
sb.writeln("case '$getter': return this.$getter;");
});
sb.writeln("default:throw 'no getter or field called \$key';");
sb.writeln('}}');
return sb.toString();
}
String _buildSquareBracketsOperatorAssignment(Set<String> setters,Set<String> readOnlyFields) {
var sb = new StringBuffer();
sb.writeln('operator []=(String key,Object value){');
sb.writeln('switch (key){');
setters.forEach((setter) {
sb.writeln("case '$setter':this.$setter=value;return;");
});
readOnlyFields.forEach((readOnlyField) {
sb.writeln("case '$readOnlyField':throw 'field \$key is read only';return;");
});
sb.writeln("default:throw 'no setter or field called \$key';");
sb.writeln('}}');
return sb.toString();
}
String _buildCallMethod(Set<MethodDeclaration> methods){
var sb = new StringBuffer();
sb.writeln('call(String key,{List<Object> required:null,List<Object> positional:null,Map<String,Object> named:null}){');
sb.writeln('switch (key){');
methods.forEach((md){
sb.writeln("case '${md.name.name}': return this.${buildMethodCallString(md)}");
});
sb.writeln("default:throw 'no setter or field called \$key';");
sb.writeln('}}');
return sb.toString();
}
}
一些实用方法:
isSubClassOrHasMixinOf(ClassDeclaration classDec, String superClassOrMixinName) {
if(classDec.withClause!=null){
print(classDec.withClause.mixinTypes);
}
if ((classDec.extendsClause != null &&
classDec.extendsClause.superclass.name.name == superClassOrMixinName) ||
(classDec.withClause != null &&
classDec.withClause.mixinTypes.any((type)=>type.name.name==superClassOrMixinName))) {
return true;
}
return false;
}
Set<String> getClassDeclarationFieldDeclarationNames(ClassDeclaration cd,
{public: true, private: false, readOnly: false}) {
var fieldNames = new Set<String>();
cd.members.forEach((m) {
if (m is FieldDeclaration) {
var fd = m as FieldDeclaration;
if ((fd.fields.isFinal || fd.fields.isConst) && readOnly || (!readOnly && !fd.fields.isFinal && !fd.fields.isConst)) {
fd.fields.variables.forEach((variable) {
var fName = variable.name.name;
if ((Identifier.isPrivateName(fName) && private) || (!Identifier.isPrivateName(fName) && public)) {
fieldNames.add(fName);
}
});
}
}
});
return fieldNames;
}
Set<FieldDeclaration> getClassDeclarationFieldDeclarations(ClassDeclaration cd,
{public: true, private: false, readOnly: false}) {
var fieldsDeclarations = new Set<FieldDeclaration>();
cd.members.forEach((m) {
if (m is FieldDeclaration) {
var fd = m as FieldDeclaration;
if ((fd.fields.isFinal || fd.fields.isConst) && readOnly || (!readOnly && !fd.fields.isFinal && !fd.fields.isConst)) {
fd.fields.variables.forEach((variable) {
if ((Identifier.isPrivateName(variable.name.name) && private) || (!Identifier.isPrivateName(variable.name.name) && public)) {
fieldsDeclarations.add(fd);
}
});
}
}
});
return fieldsDeclarations;
}
String buildMethodCallString(MethodDeclaration md){
var sb = new StringBuffer();
sb.write('${md.name.name}(');
var requiredParams = getMethodFormalParameterDeclarations(md,required:true);
if(requiredParams.length>0){
for(int i=0;i<requiredParams.length;i++){
sb.write('required[$i],');
};
}
var positionalParams = getMethodFormalParameterDeclarations(md,positional:true);
if(positionalParams.length>0){
for(int i=0;i<positionalParams.length;i++){
sb.write('(positional==null || positional[$i]==null)?${positionalParams[i].childEntities.last}:positional[$i],');
};
}
var namedParams = getMethodFormalParameterDeclarations(md,named:true);
if(namedParams.length > 0){
for(int i=0;i<namedParams.length;i++){
sb.write("${namedParams[i].identifier.name}:(named==null || !named.containsKey('${namedParams[i].identifier}'))?${namedParams[i].childEntities.last}:named['${namedParams[i].identifier}'],");
};
}
sb.write(');');
return sb.toString().replaceAll(r',)',')');
}
Set<MethodDeclaration> getClassDeclarationMethodDeclarations(
ClassDeclaration cd, {public: true, private: false, getter: false,
setter: false, operator: false, normal: false}) {
var methodDeclarations = new Set<MethodDeclaration>();
cd.members.forEach((d) {
if (d is MethodDeclaration) {
var md = d as MethodDeclaration;
var mName = md.name.name;
if ((Identifier.isPrivateName(mName) && private) ||
(!Identifier.isPrivateName(mName) && public)) {
if (md.isSetter && setter) {
methodDeclarations.add(md);
} else if (md.isGetter && getter) {
methodDeclarations.add(md);
} else if (md.isOperator && operator) {
//to do warn if [] []= are already overloaded and terminate
return;
} else if (normal && !md.isOperator && !md.isGetter && !md.isSetter) {
methodDeclarations.add(md);
}
}
}
});
return methodDeclarations;
}
Set<String> getClassDeclarationMethodDeclarationNames(
ClassDeclaration cd, {public: true, private: false, getter: false,
setter: false, operator: false, normal: false}) {
var methodDeclarationNames = new Set<String>();
cd.members.forEach((d) {
if (d is MethodDeclaration) {
var md = d as MethodDeclaration;
var mName = md.name.name;
if ((Identifier.isPrivateName(mName) && private) ||
(!Identifier.isPrivateName(mName) && public)) {
if (md.isSetter && setter) {
methodDeclarationNames.add(mName);
} else if (md.isGetter && getter) {
methodDeclarationNames.add(mName);
} else if (md.isOperator && operator) {
//to do warn if [] []= are already overloaded and terminate
return;
} else if (normal && !md.isOperator && !md.isGetter && !md.isSetter) {
methodDeclarationNames.add(mName);
}
}
}
});
return methodDeclarationNames;
}
List<FormalParameter> getMethodFormalParameterDeclarations(MethodDeclaration md,
{required: false, positional: false, named: false}) {
var formalParameters = new List<FormalParameter>();
md.parameters.parameters.forEach((fp) {
if ((fp.kind == ParameterKind.REQUIRED && required) ||
(fp.kind == ParameterKind.POSITIONAL && positional) ||
(fp.kind == ParameterKind.NAMED && named)) {
formalParameters.add(fp);
}
});
return formalParameters;
}
示例:
class Model extends Object with DynamicAccess{
String _a;
String get a => _a;
set a(v)=> _a=v;
String _b;
String get b => _b;
set b(v)=> _b=v;
final int c=9;
String _z = 'xx';
dummyMethod(int a,int b){
return 6+a+b;
}
dummyNoParams(){
return 0;
}
dummyMethodWithPositionalParams(int a,String d,[int x=4,y=6]){
return '${a+x+y} $d';
}
dummyMethodWithNamedParams(int x,int y,{int f:4,String g:'no'}){
return '${x+y+f} $g';
}
}
给出:
class Model extends Object with DynamicAccess {
String _a;
String get a => _a;
set a(v) => _a = v;
String _b;
String get b => _b;
set b(v) => _b = v;
final int c = 9;
String _z = 'xx';
dummyMethod(int a, int b) {
return 6 + a + b;
}
dummyNoParams() {
return 0;
}
dummyMethodWithPositionalParams(int a, String d, [int x = 4, y = 6]) {
return '${a+x+y} $d';
}
dummyMethodWithNamedParams(int x, int y, {int f: 4, String g: 'no'}) {
return '${x+y+f} $g';
}
operator [](String key) {
switch (key) {
case 'a':
return this.a;
case 'b':
return this.b;
case 'c':
return this.c;
default:
throw 'no getter or field called $key';
}
}
operator []=(String key, Object value) {
switch (key) {
case 'a':
this.a = value;
return;
case 'b':
this.b = value;
return;
case 'c':
throw 'field $key is read only';
return;
default:
throw 'no setter or field called $key';
}
}
call(String key, {List<Object> required: null, List<Object> positional: null,
Map<String, Object> named: null}) {
switch (key) {
case 'dummyMethod':
return this.dummyMethod(required[0], required[1]);
case 'dummyNoParams':
return this.dummyNoParams();
case 'dummyMethodWithPositionalParams':
return this.dummyMethodWithPositionalParams(required[0], required[1],
(positional == null || positional[0] == null) ? 4 : positional[0],
(positional == null || positional[1] == null) ? 6 : positional[1]);
case 'dummyMethodWithNamedParams':
return this.dummyMethodWithNamedParams(required[0], required[1],
f: (named == null || !named.containsKey('f')) ? 4 : named['f'],
g: (named == null || !named.containsKey('g')) ? 'no' : named['g']);
default:
throw 'no setter or field called $key';
}
}
}