package mill.moduledefs import scala.collection.mutable.ListBuffer import scala.reflect.internal.Flags import scala.tools.nsc.doc.ScaladocSyntaxAnalyzer import scala.tools.nsc.io.VirtualFile import scala.tools.nsc.util.BatchSourceFile import scala.tools.nsc.{Global, Phase} import scala.tools.nsc.plugins.{Plugin, PluginComponent} import scala.tools.nsc.transform.Transform class AutoOverridePlugin(val global: Global) extends Plugin { import global._ override def init(options: List[String], error: String => Unit): Boolean = true val name = "auto-override-plugin" val description = "automatically inserts `override` keywords for you" val components = List[PluginComponent]( new PluginComponent with Transform { type GT = AutoOverridePlugin.this.global.type override val global: GT = AutoOverridePlugin.this.global override val phaseName: String = "EmbedScaladocAnnotation" override val runsAfter: List[String] = List("parser") override def newTransformer(unit: global.CompilationUnit): global.Transformer = { new ScaladocTransformer } import global._ class ScaladocTransformer extends global.Transformer { val comments = new Comments() override def transformUnit(unit: CompilationUnit)= { if (unit.source.file.name.endsWith(".scala") || unit.source.file.name.endsWith(".sc")){ comments.parseComments(unit) super.transformUnit(unit) } } override def transform(tree: global.Tree): global.Tree = { super.transform(tree match { case x: global.ClassDef => comments.getComment(x.pos) match { case Some(comment) => global.treeCopy.ClassDef(tree, newMods(x.mods, comment), x.name, x.tparams, x.impl) case None => x } case x: global.ModuleDef => comments.getComment(x.pos) match { case Some(comment) => global.treeCopy.ModuleDef(tree, newMods(x.mods, comment), x.name, x.impl) case None => x } case x: global.DefDef => comments.getComment(x.pos) match { case Some(comment) => global.treeCopy.DefDef(tree, newMods(x.mods, comment), x.name, x.tparams, x.vparamss, x.tpt, x.rhs) case None => x } case x: global.ValDef => comments.getComment(x.pos) match { case Some(comment) => global.treeCopy.ValDef(tree, newMods(x.mods, comment), x.name, x.tpt, x.rhs) case None => x } case x => x }) } def newMods(old: global.Modifiers, comment: String) = { old.copy( annotations = createAnnotation(comment) :: old.annotations ) } private def createAnnotation(comment: String): global.Tree = global.Apply( global.Select( global.New( global.Select( global.Select( global.Ident( global.newTermName("mill") ), global.newTermName("moduledefs") ), global.newTypeName("Scaladoc") ) ), global.nme.CONSTRUCTOR ), List(Literal(Constant(comment))) ) } class Comments extends ScaladocSyntaxAnalyzer[global.type](global){ val comments = ListBuffer[(Position, String)]() def getComment(pos: Position): Option[String] = { val tookComments = comments.takeWhile { case (x, _) => x.end < pos.start } comments --= (tookComments) tookComments.lastOption.map(_._2) } def parseComments(unit: CompilationUnit): Unit = { comments.clear() new ScaladocUnitParser(unit, Nil) { override def newScanner = new ScaladocUnitScanner(unit, Nil) { override def registerDocComment(str: String, pos: Position) = { comments += ((pos, str)) } } }.parse() } override val runsAfter: List[String] = Nil override val runsRightAfter: Option[String] = None } }, new PluginComponent { val global = AutoOverridePlugin.this.global import global._ override val runsAfter = List("typer") override val runsBefore = List("patmat") val phaseName = "auto-override" override def newPhase(prev: Phase) = new GlobalPhase(prev) { def name: String = phaseName def isCacher(owner: Symbol) = { val baseClasses = if (owner.isClass) Some(owner.asClass.baseClasses) else if (owner.isModule) Some(owner.asModule.baseClasses) else None baseClasses.exists(_.exists(_.fullName == "mill.moduledefs.Cacher")) } def apply(unit: global.CompilationUnit): Unit = { object AutoOverrider extends global.Transformer { override def transform(tree: global.Tree) = tree match{ case d: DefDef if d.symbol.overrideChain.count(!_.isAbstract) > 1 && !d.mods.isOverride && isCacher(d.symbol.owner) => d.symbol.flags = d.symbol.flags | Flags.OVERRIDE copyDefDef(d)(mods = d.mods | Flags.OVERRIDE) case _ => super.transform(tree) } } unit.body = AutoOverrider.transform(unit.body) } } } ) }