mutator 0.0.4

mutator #

A dart language helper tool for pre-compile/transform time refactoring. A potential alternative for Macro or inline function in desperate times.

##Status: Alpha Type detection currently relies on a solution I improvised without a proper design or abstraction and it is neither fast or exhaustively tested. Skip type detection by passing skip_type_check:true to mutate_t method for safety and speed if possible;see usage example1 for more details.

Usage #

A simple usage example: Refactoring math.max(5,9) into (){int t = 5;t = t<9?9:t;return t;}();.

import 'package:mutator/mutator.dart';
String alias = 'math';
String pattern = 'math\\.max\\([0-9,\\w\\s_]+\\)';
//path needs to be set properly if there are relative file imports or part files.
String file_path = '';//leaving empty as neither is the case.

String src = """
      import 'dart:math' as math;
      main() async{
        int m = math.max(5,94,8,3,5,7,4);
      }
      """;

///main  should transform the value of
///src into the code below.
///
///   import 'dart:math' as math;
///   main() async{
///     int m = () {
///       int t = 5;
///       t = t < 94 ? 94 : t;
///       t = t < 8 ? 8 : t;
///       t = t < 3 ? 3 : t;
///       t = t < 5 ? 5 : t;
///       t = t < 7 ? 7 : t;
///       t = t < 4 ? 4 : t;
///       return t;
///     }();
///}
main() async{
  replacer(MethodInvocation e){
    String generate_code_for_getting_larger(
        String variable_name,
        String value1,
        String value2){
      return '${variable_name} = '
          '${value1.trim()}<${value2.trim()}?'
          '${value2.trim()}:${value1.trim()};';
    }
    String s = e.toString();
    s = s.substring(9,s.length-1);//removing `math.max(` and `)`
    var l = s.split(',');

    List f = ['(){int t = ${l[0]};'];

    for(String v in l.sublist(1))
      f.add(generate_code_for_getting_larger('t','t',v));

    f.add('return t;}()');
    return f.join();
  }
  var m = new Mutator<MethodInvocation>(
      '', pattern, replacer,alias_name: alias);
  print(await m.mutate_t(file_path,code:src,skip_type_check:true));
}

Refactoring r.nextInt(5) into a random number. Leaving r2.nextInt(5) unchanged as r2 is not an instance of math.Random.

import 'dart:math' as math;
import 'package:mutator/mutator.dart';

String pattern = '[\\w0-9_]+\\.nextInt\\([0-9]+\\)';
String klass_name = 'Random';
String path = '';//dummy path
String src = """
    import 'dart:math' as math;
    main() async{
    var r = new math.Random(5);
    print(r.nextInt(600));
    var r2 = new Random();
    print(r2.nextInt(600));
    }
    class Random{
        nextInt(int n){
          return n +5;
        }
    }
    """;
/// main should transform the value of src into
/// the code below and print it:
///
///import 'dart:math' as math;
///
///main() async{
///  var r = new math.Random(5);
///  print(88);//Changed
///  var r2 = new Random();
///  print(r2.nextInt(600));//Not changed
///}
///
///class Random {
///  nextInt(int n) {
///    return n + 5;
///  }
///}
///
main() async{
  int random_num;
  var r = new math.Random(5);
  replacer(MethodInvocation e){
    String s = e.toString();
    random_num = r.nextInt( int.parse(new RegExp('[0-9]+')
        .firstMatch(s).group(0)));
    return random_num.toString();
  }
  var m = new Mutator<MethodInvocation>(
      klass_name, pattern, replacer,alias_name: 'math');
  print(await m.mutate_t(path,code:src));
}

Refactoring d.on(o).hi = ()=>print('hi'); into d.on(o).set('hi',()=>print('hi'));

import 'package:mutator/mutator.dart';
String code = """
import 'package:mistletoe';
var d = new Dynamism(expert:true);
main() async{
    var o = new Object();
    d.on(o).hi = ()=>print('hi');
}
""";
const String klass_name = 'Dynamism';
const String pattern =
    '^[a-z.A-Z_0-9]+\\.on\\'
    '([a-z.A-Z_0-9]+\\)\\.[a-z.A-Z_0-9]+';
String file_path = '';
main() async{
    replacer(e){
        String s = e.toString();
        List l  = s.split('=');
        var invocation = l.removeAt(0).split('.');
        String name = invocation.removeLast().trim();
        invocation = invocation.join('.') +
            '.set(\'${name}\', ${l.join('=').trim()})';
        return invocation;
    }
    var m = new Mutator<AssignmentExpression>(
      klass_name, pattern, replacer);
    print(await m.mutate_t(file_path,code:code));
}

Features and bugs #

Please file feature requests and bugs at the https://github.com/TastyCatFood/mutator/issues.

Limitations #

  • No type detection available when the type is not statically defined. e.g.

      f(e){ return e.nextInt(4);}
    
  • Function's return type is ignored. e.g.

     math.Random f(){new math.Random(501);}
     main(){
         f().nextInt(7);
     }
    
  • Type information within conditional statement are ignored. e.g.

      f(e){
         if(e is math.Random){
          return e.nextInt(2);
         }
      }
    

Does not detect the type of variables defined in a file that has been imported as a package or a part of dart-sdk. #

e.g.

    import 'package:example_code.dart' as eg;
    main(){
    // Mutator does not look into the package to find the type of [a].
        print(eg.a);
    }

The type of [a] is available when example_code.dart is imported relatively; import './example_code.dart'; or as a part file.

Changelog #

0.0.1 #

  • Initial version

0.0.2 #

  • A bug fixed

0.0.3 #

  • mutate and mutate_t are now async methods. This change has been made as sometimes barback attempts to transform a part file before library's main file. Now, mutate_t only completes when dependencies are resolved.

0.0.4 #

  • time_out_in_seconds option added to mutate_t and mutate. mutator function hangs when a part file is passed to mutate never its main library file is never passed; dependencies cannot be resolved. time_out_in_seconds is added to throw a warning when such occurs.

example/mutator_example.dart

import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:mutator/mutator.dart';
import 'package:path/path.dart' as Path;
import 'dart:io' show Platform;

const String klass_name = 'Dynamism';
const String pattern =
    '^[a-z.A-Z_0-9]+\\.on\\'
    '([a-z.A-Z_0-9]+\\)\\.[a-z.A-Z_0-9]+';
String file_path = to_abs_path('../web/index.dart');
to_abs_path(path,[base_dir = null]){
// Fetching the project home dir
//  var cd = Path.current;

//  Changing the current directory
//  Directory original_dir = Directory.current;
//  Directory.current = dirname.toFilePath();
  Path.Context context;
  if(Platform.isWindows){
    context = new Path.Context(style:Path.Style.windows);
  }else{
    context = new Path.Context(style:Path.Style.posix);
  }
  base_dir ??= Path.dirname(
      Platform.script.toFilePath());
  path = context.join( base_dir,path);
  return context.normalize(path);
}
main() async {

  var m = new Mutator<AssignmentExpression>(
      klass_name, pattern,(e){
    String s = e.toString();
    List l  = s.split('=');
    var invocation = l.removeAt(0).split('.');
    String name = invocation.removeLast().trim();
    invocation = invocation.join('.') +
        '.set(\'${name}\', ${l.join('=').trim()})';
    return invocation;
  });
  String r = await m.mutate_t(file_path);


  m = new Mutator<PropertyAccess>(klass_name,pattern,
        (e){
      List l = e.toString().split('.');
      String property_name = l.removeLast();
      String invocation = l.join('.');
      invocation = invocation +
          '.get(\'${property_name.trim()}\')';
      return invocation;
    });
  r = await m.mutate_t(file_path,code:r);


  m = new Mutator<MethodInvocation>(klass_name,pattern,
      (MethodInvocation e){
          String s = e.toString();
          var m = new RegExp(
              'on\\([\\w_\\.]+\\)\\.').firstMatch(s);
          //splitting d.on(e).hi(e) into `d.on(e)`
          // and `hi(e)`
          String on_call = s.substring(0,m.end-1);
          String method_call = s.substring(m.end,s.length);

          //splitting `hi(e)` into `hi` and `(e)`
          int idx = method_call.indexOf('(');
          String method_name =
          method_call.substring(0,idx).trim();
          String params = method_call.substring(idx+1,
              method_call.length-1);
          //assembling parts into a method call
          return '${on_call}.invoke'
              '(\'${method_name}\',[${params}])';
  });
  r = await m.mutate_t(file_path,code:r);
  print(r);
  return;
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  mutator: ^0.0.4

2. Install it

You can install packages from the command line:

with pub:


$ pub get

Alternatively, your editor might support pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:mutator/mutator.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
0
Health:
Code health derived from static analysis. [more]
--
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
--
Overall:
Weighted score of the above. [more]
0
Learn more about scoring.

The package version is not analyzed, because it does not support Dart 2. Until this is resolved, the package will receive a health and maintenance score of 0.

Analysis issues and suggestions

Support Dart 2 in pubspec.yaml.

The SDK constraint in pubspec.yaml doesn't allow the Dart 2.0.0 release. For information about upgrading it to be Dart 2 compatible, please see https://dart.dev/dart-2#migration.

Maintenance issues and suggestions

Make sure dartdoc successfully runs on your package's source files. (-10 points)

Dependencies were not resolved.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=1.0.0 <2.0.0

Admin