-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Generalize main annotations #13727
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Generalize main annotations #13727
Changes from all commits
Commits
Show all changes
121 commits
Select commit
Hold shift + click to select a range
3673ccf
Create classes for improved main annotations
timotheeandres 65785d4
Fix parameter error in argsGetter
timotheeandres e9db3d0
Allow no parameters
timotheeandres 5d3c199
Add first tests for new main annotation
timotheeandres 6161b9b
Add tests for incorrect parameters passing
timotheeandres 959aff9
Update MiMa filters
timotheeandres 81c88c1
Split argGetter with/out default value
timotheeandres 2fbdf66
Curry parsers with using
timotheeandres 098fd27
Add more tests: no parentheses, by-name params
timotheeandres 754a6d0
Add override keyword for safety
timotheeandres c8210d5
Test for unknown given parser
timotheeandres b727dab
Add class for main method exit code
timotheeandres ad291f2
Add tests for return types and overloading
timotheeandres c68d6f0
Extract usage and explain into main
timotheeandres 1ca13aa
Extract run into main
timotheeandres 891c6e4
Make Argument a trait, make name a def
timotheeandres d35237e
Update main code generation
timotheeandres 1ebf1e5
Update tests with Java reflection (do not pass yet)
timotheeandres 58d5460
Add argument type to Argument trait
timotheeandres 652e1e0
Fix return type of vararg func
timotheeandres 8ac03b7
Implement Scaladoc parsing
timotheeandres b9cc156
Add tests for top-level main, currying and genericity
timotheeandres e7d9bba
Simplify code in createValArgs
timotheeandres a05c4f1
Add rules to MiMa filters
timotheeandres cb4bb20
Fix tests overriding main methods
timotheeandres c5bed16
Remove debug println
timotheeandres c0d80d3
Fix vararg handling in MainProxies
timotheeandres c22d360
Cleaner self reference in main.scala
timotheeandres 884ea20
Fix reflection in test
timotheeandres f18b3a4
Generate class instead of object, instanciate main
timotheeandres a3748d0
Instanciate correct class of annotation
timotheeandres a60bd11
Fix test outputs with new doc format
timotheeandres 989bcfa
Add tests for multiple annotations, non-method
timotheeandres 26b2d63
Clarification of arg generation
timotheeandres 45f7ba2
Add vararg and default to top-level test
timotheeandres 62b1670
Add support for default values
timotheeandres 45490a9
Add "(optional)" for args with default values
timotheeandres 7cf2f7f
Make help test more extensive
timotheeandres 0ab2c43
Fix help test
timotheeandres 0657c64
Implement doc line wraping
timotheeandres ce7471a
Add support for multiline arg doc
timotheeandres 8ed3a9f
Remove {{ and }} from docstrings
timotheeandres 8b68474
Remove wildcards from Argument matching
timotheeandres 7eb8c0c
Extract default values getter for clarity
timotheeandres 91f5ee4
Factorize getter code, check duplicate named param
timotheeandres cd5674e
Make MainProxy more explicit
timotheeandres ccfe578
Change explain formatting
timotheeandres 8b42578
Use f.foo(i) instead of f foo i
timotheeandres 91f6d55
Trim docstring after removing {{ and }}
timotheeandres 87f9c09
Add test for anon. func. and implicit/using @main
timotheeandres 78fa961
Factorize createArgs
timotheeandres a65b76f
Rework of Arguments, change usage format
timotheeandres 52ef5dc
Misc. changes to tests
timotheeandres 75e06a1
Forbid multiple @main annotations
timotheeandres 395fd14
Make defaultValue by-name param
timotheeandres f300597
Remove redundant val in ExitCode
timotheeandres 45acb09
Fix method call notation
timotheeandres 6de547f
Generate code for children of MainAnnotation, not main
timotheeandres 70ee28a
Forbid curried main methods
timotheeandres 50de840
Add missing arrow in main.argGetterDefault
timotheeandres 6bc549b
Make Command a trait
timotheeandres 1b9c952
Fix issue where map is not member of Array[String | Null] | Null
timotheeandres de93b4d
Add test for multiple runs
timotheeandres bbcf4ea
Add test from doc example
timotheeandres bdddb3f
Make main class final
timotheeandres 88a76df
Factorize code of main
timotheeandres f2bafb4
Move Command out of MainAnnotation
timotheeandres 3cd2a88
Make by-name parameter a function for clarity
timotheeandres c4a6ff0
Print returned value when it is not Unit
timotheeandres 7b685c4
Add more parser checks
timotheeandres a6e9da9
Make usage clearer by printing type
timotheeandres 5065a09
Add help tests with homemade classes
timotheeandres 63f30f8
Simplify main-annotation-help.scala
timotheeandres de73f3c
Update MiMa filters
timotheeandres 2cad3dc
Add test for docstring starting with //
timotheeandres d32868a
Pass doc to command; main parameter for line length
timotheeandres 4d4418e
Add constructor to main and update MiMa
timotheeandres bded202
Make Documentation a trait and use anonymous instance
timotheeandres aff99de
Add test for Option[T] and Either[T]
timotheeandres 889dbd6
Remove ExitCode and result printing in main
timotheeandres 4d0c149
Add test for function Parser
timotheeandres 1be2e7d
Move Documentation back to MainProxies
timotheeandres 8161163
Add test with Future[_] as return type
timotheeandres eafa508
Factorize annotation instanciation code
timotheeandres 99d9c12
Pass object to argGetter instead of multiple params
timotheeandres 282bfc6
Move code around for readability
timotheeandres 0d1d1f9
Fix symbol error in Documentation
timotheeandres c8584df
Add main.Arg annotation for argument-related parameters
timotheeandres d1ab483
Move parameter documentation out of constructor
timotheeandres f2b5287
Update example in MainProxies
timotheeandres 635b79e
Simplify code for assignations
timotheeandres 485d9f8
Add documentation to MainProxies
timotheeandres 1e71cb2
Parametrize -- in main
timotheeandres b4dc846
Use types in MainProxies for convenience
timotheeandres 760f49c
Use default values' symbols instead of trees
timotheeandres af851b3
Change structure of ParameterInfos
timotheeandres 300adcf
Add test to check lazyness of default value
timotheeandres e818f13
Simplify homemade annotations tests
timotheeandres 07426c4
Split Arg into ShortName and Name
timotheeandres cf0c98c
Rename defaultValueOpt for clearer name
timotheeandres 99f6ba3
Fix issue with explicit type for parameters
timotheeandres 2be6820
Add crazy test for param annotations
timotheeandres c681a6f
Make Name and ShortName normal classes (remove case)
timotheeandres 5b44edf
Update comments in MainProxies
timotheeandres 6f16275
Quick clean up of MainProxies
timotheeandres 86c6bed
Change default value index extraction
timotheeandres fc1fba6
Reformat switch cases
timotheeandres f1bc063
Remove unnecessary code
timotheeandres 61744dd
Avoid vars in code
timotheeandres 06514c4
Keep old version of MainProxies for #14156
timotheeandres 5599dd5
Add more thorough documentation for main
timotheeandres 823f320
Allow only standard letters as short names
timotheeandres a8da6b8
Rename docComment for clarity
timotheeandres 36988bb
Move ArgumentKind inside Command
timotheeandres ddbb6d0
Add function to check name validity
timotheeandres a57ff52
Pass ParameterInfos in command instead of getters
timotheeandres c77f5d3
Move helper functions inside methods
timotheeandres 5558948
Rework of main.Name and main.ShortName
timotheeandres 4e99de0
Remove maxLineLength from main
timotheeandres b359465
Add -h to display help
timotheeandres 4ce8911
Move helper functions' code inside run
timotheeandres File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3,30 +3,33 @@ package ast | |||||
|
||||||
import core._ | ||||||
import Symbols._, Types._, Contexts._, Decorators._, util.Spans._, Flags._, Constants._ | ||||||
import StdNames.nme | ||||||
import StdNames.{nme, tpnme} | ||||||
import ast.Trees._ | ||||||
import Names.{Name, TermName} | ||||||
import Comments.Comment | ||||||
import NameKinds.DefaultGetterName | ||||||
import Annotations.Annotation | ||||||
|
||||||
/** Generate proxy classes for @main functions. | ||||||
* A function like | ||||||
* | ||||||
* @main def f(x: S, ys: T*) = ... | ||||||
* | ||||||
* would be translated to something like | ||||||
* | ||||||
* import CommandLineParser._ | ||||||
* class f { | ||||||
* @static def main(args: Array[String]): Unit = | ||||||
* try | ||||||
* f( | ||||||
* parseArgument[S](args, 0), | ||||||
* parseRemainingArguments[T](args, 1): _* | ||||||
* ) | ||||||
* catch case err: ParseError => showError(err) | ||||||
* } | ||||||
*/ | ||||||
object MainProxies { | ||||||
|
||||||
def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { | ||||||
/** Generate proxy classes for @main functions. | ||||||
* A function like | ||||||
* | ||||||
* @main def f(x: S, ys: T*) = ... | ||||||
* | ||||||
* would be translated to something like | ||||||
* | ||||||
* import CommandLineParser._ | ||||||
* class f { | ||||||
* @static def main(args: Array[String]): Unit = | ||||||
* try | ||||||
* f( | ||||||
* parseArgument[S](args, 0), | ||||||
* parseRemainingArguments[T](args, 1): _* | ||||||
* ) | ||||||
* catch case err: ParseError => showError(err) | ||||||
* } | ||||||
*/ | ||||||
def mainProxiesOld(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { | ||||||
import tpd._ | ||||||
def mainMethods(stats: List[Tree]): List[Symbol] = stats.flatMap { | ||||||
case stat: DefDef if stat.symbol.hasAnnotation(defn.MainAnnot) => | ||||||
|
@@ -36,11 +39,11 @@ object MainProxies { | |||||
case _ => | ||||||
Nil | ||||||
} | ||||||
mainMethods(stats).flatMap(mainProxy) | ||||||
mainMethods(stats).flatMap(mainProxyOld) | ||||||
} | ||||||
|
||||||
import untpd._ | ||||||
def mainProxy(mainFun: Symbol)(using Context): List[TypeDef] = { | ||||||
def mainProxyOld(mainFun: Symbol)(using Context): List[TypeDef] = { | ||||||
val mainAnnotSpan = mainFun.getAnnotation(defn.MainAnnot).get.tree.span | ||||||
def pos = mainFun.sourcePos | ||||||
val argsRef = Ident(nme.args) | ||||||
|
@@ -115,4 +118,311 @@ object MainProxies { | |||||
} | ||||||
result | ||||||
} | ||||||
|
||||||
private type DefaultValueSymbols = Map[Int, Symbol] | ||||||
private type ParameterAnnotationss = Seq[Seq[Annotation]] | ||||||
|
||||||
/** | ||||||
* Generate proxy classes for main functions. | ||||||
* A function like | ||||||
* | ||||||
* /** | ||||||
* * Lorem ipsum dolor sit amet | ||||||
* * consectetur adipiscing elit. | ||||||
* * | ||||||
* * @param x my param x | ||||||
* * @param ys all my params y | ||||||
* */ | ||||||
* @main(80) def f( | ||||||
* @main.Alias("myX") x: S, | ||||||
* ys: T* | ||||||
* ) = ... | ||||||
* | ||||||
* would be translated to something like | ||||||
* | ||||||
* final class f { | ||||||
* static def main(args: Array[String]): Unit = { | ||||||
* val cmd = new main(80).command( | ||||||
* args, | ||||||
* "f", | ||||||
* "Lorem ipsum dolor sit amet consectetur adipiscing elit.", | ||||||
* new scala.annotation.MainAnnotation.ParameterInfos("x", "S") | ||||||
* .withDocumentation("my param x") | ||||||
* .withAnnotations(new scala.main.Alias("myX")), | ||||||
* new scala.annotation.MainAnnotation.ParameterInfos("ys", "T") | ||||||
* .withDocumentation("all my params y") | ||||||
* ) | ||||||
* | ||||||
* val args0: () => S = cmd.argGetter[S]("x", None) | ||||||
* val args1: () => Seq[T] = cmd.varargGetter[T]("ys") | ||||||
* | ||||||
* cmd.run(f(args0(), args1()*)) | ||||||
* } | ||||||
* } | ||||||
*/ | ||||||
def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { | ||||||
import tpd._ | ||||||
|
||||||
/** | ||||||
* Computes the symbols of the default values of the function. Since they cannot be infered anymore at this | ||||||
* point of the compilation, they must be explicitely passed by [[mainProxy]]. | ||||||
*/ | ||||||
def defaultValueSymbols(scope: Tree, funSymbol: Symbol): DefaultValueSymbols = | ||||||
scope match { | ||||||
case TypeDef(_, template: Template) => | ||||||
template.body.flatMap((_: Tree) match { | ||||||
case dd: DefDef if dd.name.is(DefaultGetterName) && dd.name.firstPart == funSymbol.name => | ||||||
val DefaultGetterName.NumberedInfo(index) = dd.name.info | ||||||
List(index -> dd.symbol) | ||||||
case _ => Nil | ||||||
}).toMap | ||||||
case _ => Map.empty | ||||||
} | ||||||
|
||||||
/** Computes the list of main methods present in the code. */ | ||||||
def mainMethods(scope: Tree, stats: List[Tree]): List[(Symbol, ParameterAnnotationss, DefaultValueSymbols, Option[Comment])] = stats.flatMap { | ||||||
case stat: DefDef => | ||||||
val sym = stat.symbol | ||||||
sym.annotations.filter(_.matches(defn.MainAnnot)) match { | ||||||
case Nil => | ||||||
Nil | ||||||
case _ :: Nil => | ||||||
val paramAnnotations = stat.paramss.flatMap(_.map( | ||||||
valdef => valdef.symbol.annotations.filter(_.matches(defn.MainAnnotParameterAnnotation)) | ||||||
)) | ||||||
(sym, paramAnnotations.toVector, defaultValueSymbols(scope, sym), stat.rawComment) :: Nil | ||||||
case mainAnnot :: others => | ||||||
report.error(s"method cannot have multiple main annotations", mainAnnot.tree) | ||||||
Nil | ||||||
} | ||||||
case stat @ TypeDef(_, impl: Template) if stat.symbol.is(Module) => | ||||||
mainMethods(stat, impl.body) | ||||||
case _ => | ||||||
Nil | ||||||
} | ||||||
|
||||||
// Assuming that the top-level object was already generated, all main methods will have a scope | ||||||
mainMethods(EmptyTree, stats).flatMap(mainProxy) | ||||||
} | ||||||
|
||||||
def mainProxy(mainFun: Symbol, paramAnnotations: ParameterAnnotationss, defaultValueSymbols: DefaultValueSymbols, docComment: Option[Comment])(using Context): List[TypeDef] = { | ||||||
val mainAnnot = mainFun.getAnnotation(defn.MainAnnot).get | ||||||
def pos = mainFun.sourcePos | ||||||
val cmdName: TermName = Names.termName("cmd") | ||||||
|
||||||
val documentation = new Documentation(docComment) | ||||||
|
||||||
/** A literal value (Boolean, Int, String, etc.) */ | ||||||
inline def lit(any: Any): Literal = Literal(Constant(any)) | ||||||
|
||||||
/** None */ | ||||||
inline def none: Tree = ref(defn.NoneModule.termRef) | ||||||
|
||||||
/** Some(value) */ | ||||||
inline def some(value: Tree): Tree = Apply(ref(defn.SomeClass.companionModule.termRef), value) | ||||||
timotheeandres marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** () => value */ | ||||||
def unitToValue(value: Tree): Tree = | ||||||
val anonName = nme.ANON_FUN | ||||||
val defdef = DefDef(anonName, List(Nil), TypeTree(), value) | ||||||
Block(defdef, Closure(Nil, Ident(anonName), EmptyTree)) | ||||||
|
||||||
/** | ||||||
* Creates a list of references and definitions of arguments, the first referencing the second. | ||||||
* The goal is to create the | ||||||
* `val args0: () => S = cmd.argGetter[S]("x", None)` | ||||||
* part of the code. | ||||||
* For each tuple, the first element is a ref to `args0`, the second is the whole definition, the third | ||||||
* is the ParameterInfos definition associated to this argument. | ||||||
*/ | ||||||
def createArgs(mt: MethodType, cmdName: TermName): List[(Tree, ValDef, Tree)] = | ||||||
mt.paramInfos.zip(mt.paramNames).zipWithIndex.map { | ||||||
case ((formal, paramName), n) => | ||||||
val argName = nme.args ++ n.toString | ||||||
val isRepeated = formal.isRepeatedParam | ||||||
|
||||||
val (argRef, formalType, getterSym) = { | ||||||
val argRef0 = Apply(Ident(argName), Nil) | ||||||
if formal.isRepeatedParam then | ||||||
(repeated(argRef0), formal.argTypes.head, defn.MainAnnotCommand_varargGetter) | ||||||
else (argRef0, formal, defn.MainAnnotCommand_argGetter) | ||||||
} | ||||||
|
||||||
// The ParameterInfos | ||||||
val parameterInfos = { | ||||||
val param = paramName.toString | ||||||
val paramInfosTree = New( | ||||||
TypeTree(defn.MainAnnotParameterInfos.typeRef), | ||||||
// Arguments to be passed to ParameterInfos' constructor | ||||||
List(List(lit(param), lit(formalType.show))) | ||||||
) | ||||||
|
||||||
/* | ||||||
* Assignations to be made after the creation of the ParameterInfos. | ||||||
* For example: | ||||||
* args0paramInfos.withDocumentation("my param x") | ||||||
* is represented by the pair | ||||||
* defn.MainAnnotationParameterInfos_withDocumentation -> List(lit("my param x")) | ||||||
*/ | ||||||
var assignations: List[(Symbol, List[Tree])] = Nil | ||||||
for (doc <- documentation.argDocs.get(param)) | ||||||
assignations = (defn.MainAnnotationParameterInfos_withDocumentation -> List(lit(doc))) :: assignations | ||||||
|
||||||
val instanciatedAnnots = paramAnnotations(n).map(instanciateAnnotation).toList | ||||||
if instanciatedAnnots.nonEmpty then | ||||||
assignations = (defn.MainAnnotationParameterInfos_withAnnotations -> instanciatedAnnots) :: assignations | ||||||
|
||||||
assignations.foldLeft[Tree](paramInfosTree){ case (tree, (setterSym, values)) => Apply(Select(tree, setterSym.name), values) } | ||||||
} | ||||||
|
||||||
val argParams = | ||||||
if formal.isRepeatedParam then | ||||||
List(lit(paramName.toString)) | ||||||
else | ||||||
val defaultValueGetterOpt = defaultValueSymbols.get(n) match { | ||||||
case None => | ||||||
none | ||||||
case Some(dvSym) => | ||||||
some(unitToValue(ref(dvSym.termRef))) | ||||||
} | ||||||
List(lit(paramName.toString), defaultValueGetterOpt) | ||||||
|
||||||
val argDef = ValDef( | ||||||
argName, | ||||||
TypeTree(), | ||||||
Apply(TypeApply(Select(Ident(cmdName), getterSym.name), TypeTree(formalType) :: Nil), argParams), | ||||||
) | ||||||
|
||||||
(argRef, argDef, parameterInfos) | ||||||
} | ||||||
end createArgs | ||||||
|
||||||
/** Turns an annotation (e.g. `@main(40)`) into an instance of the class (e.g. `new scala.main(40)`). */ | ||||||
def instanciateAnnotation(annot: Annotation): Tree = | ||||||
val argss = { | ||||||
def recurse(t: tpd.Tree, acc: List[List[Tree]]): List[List[Tree]] = t match { | ||||||
case Apply(t, args: List[tpd.Tree]) => recurse(t, extractArgs(args) :: acc) | ||||||
case _ => acc | ||||||
} | ||||||
|
||||||
def extractArgs(args: List[tpd.Tree]): List[Tree] = | ||||||
args.flatMap { | ||||||
case Typed(SeqLiteral(varargs, _), _) => varargs.map(arg => TypedSplice(arg)) | ||||||
case arg: Select if arg.name.is(DefaultGetterName) => Nil // Ignore default values, they will be added later by the compiler | ||||||
case arg => List(TypedSplice(arg)) | ||||||
} | ||||||
|
||||||
recurse(annot.tree, Nil) | ||||||
} | ||||||
|
||||||
New(TypeTree(annot.symbol.typeRef), argss) | ||||||
end instanciateAnnotation | ||||||
|
||||||
var result: List[TypeDef] = Nil | ||||||
if (!mainFun.owner.isStaticOwner) | ||||||
report.error(s"main method is not statically accessible", pos) | ||||||
else { | ||||||
var args: List[ValDef] = Nil | ||||||
var mainCall: Tree = ref(mainFun.termRef) | ||||||
var parameterInfoss: List[Tree] = Nil | ||||||
|
||||||
mainFun.info match { | ||||||
case _: ExprType => | ||||||
case mt: MethodType => | ||||||
if (mt.isImplicitMethod) { | ||||||
report.error(s"main method cannot have implicit parameters", pos) | ||||||
} | ||||||
else mt.resType match { | ||||||
case restpe: MethodType => | ||||||
report.error(s"main method cannot be curried", pos) | ||||||
Nil | ||||||
case _ => | ||||||
val (argRefs, argVals, paramInfoss) = createArgs(mt, cmdName).unzip3 | ||||||
args = argVals | ||||||
mainCall = Apply(mainCall, argRefs) | ||||||
parameterInfoss = paramInfoss | ||||||
} | ||||||
case _: PolyType => | ||||||
report.error(s"main method cannot have type parameters", pos) | ||||||
case _ => | ||||||
report.error(s"main can only annotate a method", pos) | ||||||
} | ||||||
|
||||||
val cmd = ValDef( | ||||||
cmdName, | ||||||
TypeTree(), | ||||||
Apply( | ||||||
Select(instanciateAnnotation(mainAnnot), defn.MainAnnot_command.name), | ||||||
Ident(nme.args) :: lit(mainFun.showName) :: lit(documentation.mainDoc) :: parameterInfoss | ||||||
) | ||||||
) | ||||||
val run = Apply(Select(Ident(cmdName), defn.MainAnnotCommand_run.name), mainCall) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
val body = Block(cmd :: args, run) | ||||||
val mainArg = ValDef(nme.args, TypeTree(defn.ArrayType.appliedTo(defn.StringType)), EmptyTree) | ||||||
.withFlags(Param) | ||||||
/** Replace typed `Ident`s that have been typed with a TypeSplice with the reference to the symbol. | ||||||
* The annotations will be retype-checked in another scope that may not have the same imports. | ||||||
*/ | ||||||
def insertTypeSplices = new TreeMap { | ||||||
override def transform(tree: Tree)(using Context): Tree = tree match | ||||||
case tree: tpd.Ident @unchecked => TypedSplice(tree) | ||||||
case tree => super.transform(tree) | ||||||
} | ||||||
val annots = mainFun.annotations | ||||||
.filterNot(_.matches(defn.MainAnnot)) | ||||||
.map(annot => insertTypeSplices.transform(annot.tree)) | ||||||
val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) | ||||||
.withFlags(JavaStatic) | ||||||
.withAnnotations(annots) | ||||||
val mainTempl = Template(emptyConstructor, Nil, Nil, EmptyValDef, mainMeth :: Nil) | ||||||
val mainCls = TypeDef(mainFun.name.toTypeName, mainTempl) | ||||||
.withFlags(Final | Invisible) | ||||||
if (!ctx.reporter.hasErrors) result = mainCls.withSpan(mainAnnot.tree.span.toSynthetic) :: Nil | ||||||
} | ||||||
result | ||||||
} | ||||||
|
||||||
/** A class responsible for extracting the docstrings of a method. */ | ||||||
private class Documentation(docComment: Option[Comment]): | ||||||
import util.CommentParsing._ | ||||||
|
||||||
/** The main part of the documentation. */ | ||||||
lazy val mainDoc: String = _mainDoc | ||||||
/** The parameters identified by @param. Maps from parameter name to its documentation. */ | ||||||
lazy val argDocs: Map[String, String] = _argDocs | ||||||
|
||||||
private var _mainDoc: String = "" | ||||||
private var _argDocs: Map[String, String] = Map() | ||||||
|
||||||
docComment match { | ||||||
case Some(comment) => if comment.isDocComment then parseDocComment(comment.raw) else _mainDoc = comment.raw | ||||||
case None => | ||||||
} | ||||||
|
||||||
private def cleanComment(raw: String): String = | ||||||
var lines: Seq[String] = raw.trim.split('\n').toSeq | ||||||
lines = lines.map(l => l.substring(skipLineLead(l, -1), l.length).trim) | ||||||
var s = lines.foldLeft("") { | ||||||
case ("", s2) => s2 | ||||||
case (s1, "") if s1.last == '\n' => s1 // Multiple newlines are kept as single newlines | ||||||
case (s1, "") => s1 + '\n' | ||||||
case (s1, s2) if s1.last == '\n' => s1 + s2 | ||||||
case (s1, s2) => s1 + ' ' + s2 | ||||||
} | ||||||
s.replaceAll(raw"\[\[", "").replaceAll(raw"\]\]", "").trim | ||||||
|
||||||
private def parseDocComment(raw: String): Unit = | ||||||
// Positions of the sections (@) in the docstring | ||||||
val tidx: List[(Int, Int)] = tagIndex(raw) | ||||||
|
||||||
// Parse main comment | ||||||
var mainComment: String = raw.substring(skipLineLead(raw, 0), startTag(raw, tidx)) | ||||||
_mainDoc = cleanComment(mainComment) | ||||||
|
||||||
// Parse arguments comments | ||||||
val argsCommentsSpans: Map[String, (Int, Int)] = paramDocs(raw, "@param", tidx) | ||||||
val argsCommentsTextSpans = argsCommentsSpans.view.mapValues(extractSectionText(raw, _)) | ||||||
val argsCommentsTexts = argsCommentsTextSpans.mapValues({ case (beg, end) => raw.substring(beg, end) }) | ||||||
_argDocs = argsCommentsTexts.mapValues(cleanComment(_)).toMap | ||||||
end Documentation | ||||||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.