• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

carueda / tscfg / 4156663153

pending completion
4156663153

Pull #212

github

GitHub
Merge 57db78d05 into 8d5a6b3bf
Pull Request #212: Update sbt-scoverage to 2.0.7

861 of 892 relevant lines covered (96.52%)

1.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.28
/src/main/scala/tscfg/generators/scala/ScalaGen.scala
1
package tscfg.generators.scala
2

3
import tscfg.codeDefs.scalaDef
4
import tscfg.generators._
5
import tscfg.model._
6
import tscfg.ns.NamespaceMan
7
import tscfg.util.escapeString
8
import tscfg.{ModelBuilder, model}
9

10
class ScalaGen(
11
    genOpts: GenOpts,
12
    implicit val rootNamespace: NamespaceMan = new NamespaceMan
13
) extends Generator(genOpts) {
14

15
  val accessors = new Accessors
2✔
16

17
  import defs._
18

19
  implicit val methodNames: MethodNames = MethodNames()
2✔
20
  val getter: Getter                    = Getter(genOpts, hasPath, accessors)
2✔
21

22
  import methodNames._
23

24
  val scalaUtil: ScalaUtil = new ScalaUtil(useBackticks = genOpts.useBackticks)
2✔
25

26
  import scalaUtil.{getClassName, scalaIdentifier}
27

28
  def padScalaIdLength(implicit symbols: List[String]): Int =
29
    if (symbols.isEmpty) 0
×
30
    else
31
      symbols.map(scalaIdentifier).maxBy(_.length).length
2✔
32

33
  def padId(id: String)(implicit symbols: List[String]): String =
34
    id + (" " * (padScalaIdLength - id.length))
2✔
35

36
  def generate(objectType: ObjectType): GenResult = {
37
    genResults = GenResult()
2✔
38

39
    checkUserSymbol(className)
2✔
40
    val res = generateForObj(objectType, className = className, isRoot = true)
2✔
41

42
    val packageStr = s"package ${genOpts.packageName}\n\n"
43

44
    val definition = (packageStr + res.definition).trim
2✔
45
    genResults.copy(code = definition)
2✔
46
  }
47

48
  private def generate(
49
      typ: Type,
50
      classNamesPrefix: List[String],
51
      className: String,
52
      annTypeForParentClassName: Option[AnnType] = None,
53
      parentClassMembers: Option[Map[String, model.AnnType]] = None,
54
  ): Res = typ match {
55

56
    case et: EnumObjectType => generateForEnum(et, classNamesPrefix, className)
1✔
57

58
    case ot: ObjectType =>
59
      generateForObj(
2✔
60
        ot,
61
        classNamesPrefix,
62
        className,
63
        annTypeForParentClassName = annTypeForParentClassName,
64
        parentClassMembers = parentClassMembers
65
      )
66

67
    case aot: AbstractObjectType =>
68
      generateForAbstractObj(
2✔
69
        aot,
70
        classNamesPrefix,
71
        className,
72
        annTypeForParentClassName,
73
        parentClassMembers
74
      );
75

76
    case ort: ObjectRefType => generateForObjRef(ort, classNamesPrefix)
2✔
77

78
    case lt: ListType => generateForList(lt, classNamesPrefix, className)
2✔
79

80
    case bt: BasicType => generateForBasic(bt)
2✔
81
  }
82

83
  def buildClassMembersString(
84
      classData: List[(String, Res, AnnType, Boolean)],
85
      padId: String => String,
86
      isAbstractClass: Boolean = false
87
  ): String = {
88
    classData
89
      .flatMap { case (symbol, res, a, isChildClass) =>
90
        if (a.isDefine) None
91
        else
92
          Some {
93
            val scalaId = scalaIdentifier(symbol)
94
            val modifiers = (isChildClass, isAbstractClass) match {
95
              case (true, _) =>
96
                /* The field will be overridden, therefore it needs override modifier and "val" keyword */
97
                "override val "
98
              case (false, true) =>
99
                /* The field does not override anything, but is part of abstract class => needs "val" keyword */
100
                "val "
101
              case _ => ""
102
            }
103
            val typeName = a.t match {
104
              case ot: ObjectRefType =>
105
                getClassNameForObjectRefType(ot)
106
              case _ => res.scalaType.toString
107
            }
108
            val type_ =
109
              if (a.optional && a.default.isEmpty) s"scala.Option[$typeName]"
110
              else typeName
111

112
            genResults = genResults
113
              .copy(fields = genResults.fields + (scalaId -> type_))
114
            modifiers + padId(scalaId) + " : " + type_ + dbg("E")
115
          }
116
      }
117
      .mkString(",\n  ")
2✔
118
  }
119

120
  private def generateForObj(
121
      ot: ObjectType,
122
      classNamesPrefix: List[String] = List.empty,
123
      className: String,
124
      isRoot: Boolean = false,
125
      annTypeForParentClassName: Option[AnnType] = None,
126
      parentClassMembers: Option[Map[String, model.AnnType]] = None
127
  ): Res = {
128

129
    genResults = genResults.copy(classNames = genResults.classNames + className)
2✔
130

131
    implicit val symbols: List[String] = ot.members.keys.toList.sorted
2✔
132
    symbols.foreach(checkUserSymbol)
2✔
133

134
    val results = symbols.map { symbol =>
2✔
135
      val a = ot.members(symbol)
2✔
136
      val res = generate(
2✔
137
        a.t,
2✔
138
        classNamesPrefix = className + "." :: classNamesPrefix,
2✔
139
        className = getClassName(symbol),
2✔
140
        Some(a),
2✔
141
        a.parentClassMembers,
2✔
142
      )
143
      (symbol, res, a, false)
2✔
144
    }
145

146
    val parentClassMemberResults = parentClassMembers
147
      .map(_.keys.toList.sorted)
2✔
148
      .map(parentSymbols => {
149
        parentSymbols.foreach(checkUserSymbol)
2✔
150
        parentSymbols.map { symbol =>
2✔
151
          val a = parentClassMembers.get(symbol)
2✔
152
          val res = generate(
2✔
153
            a.t,
2✔
154
            classNamesPrefix = className + "." :: classNamesPrefix,
2✔
155
            className = getClassName(symbol),
2✔
156
            Some(a),
2✔
157
            a.parentClassMembers,
2✔
158
          )
159
          (symbol, res, a, true)
2✔
160
        }
161
      })
162
      .getOrElse(List.empty)
2✔
163

164
    val classMembersStr =
165
      buildClassMembersString(parentClassMemberResults ++ results, padId)
2✔
166

167
    val parentClassString =
168
      buildParentClassString(
2✔
169
        annTypeForParentClassName,
170
        parentClassMemberResults
171
      )
172

173
    val classStr =
174
      s"""final case class $className(
175
         |  $classMembersStr
176
         |)$parentClassString
177
         |""".stripMargin
2✔
178

179
    implicit val listAccessors =
180
      collection.mutable.LinkedHashMap[String, String]()
2✔
181

182
    val objectMembersStr = (results ++ parentClassMemberResults)
183
      .flatMap { case (symbol, res, a, parentOv) =>
184
        if (a.isDefine) None
185
        else
186
          Some {
187
            val scalaId = scalaIdentifier(symbol)
188
            padId(scalaId) + " = " + getter.instance(
189
              a,
190
              res,
191
              path = escapeString(symbol)
192
            )
193
          }
194
      }
195
      .mkString(",\n      ")
2✔
196

197
    val innerClassesStr = {
198
      val defs = results.map(_._2.definition).filter(_.nonEmpty)
2✔
199
      if (defs.isEmpty) ""
2✔
200
      else {
201
        "\n  " + defs.mkString("\n").replaceAll("\n", "\n  ")
2✔
202
      }
203
    }
204

205
    val elemAccessorsStr = {
206
      val objOnes =
207
        if (listAccessors.isEmpty) ""
2✔
208
        else {
209
          "\n" + listAccessors.keys.toList.sorted
2✔
210
            .map { methodName =>
211
              listAccessors(methodName)
212
            }
213
            .mkString("\n")
2✔
214
        }
215
      val rootOnes =
216
        if (!isRoot) ""
2✔
217
        else {
218
          if (accessors.rootListAccessors.isEmpty) ""
2✔
219
          else {
220
            "\n\n" + accessors.rootListAccessors.keys.toList.sorted
2✔
221
              .map { methodName =>
222
                accessors.rootListAccessors(methodName)
223
              }
224
              .mkString("\n")
2✔
225
          }
226
        }
227
      objOnes + rootOnes
2✔
228
    }
229

230
    val rootAuxClasses = if (isRoot) {
231
      scalaDef("$TsCfgValidator")
2✔
232
    }
233
    else ""
2✔
234

235
    val (ctorParams, errHandlingDecl, errHandlingDispatch) = if (isRoot) {
2✔
236
      (
237
        "c: com.typesafe.config.Config",
238
        """val $tsCfgValidator: $TsCfgValidator = new $TsCfgValidator()
239
          |    val parentPath: java.lang.String = ""
240
          |    val $result = """.stripMargin,
241
        s"""
242
           |    $$tsCfgValidator.validate()
243
           |    $$result""".stripMargin
244
      )
245
    }
246
    else
247
      (
248
        "c: com.typesafe.config.Config, parentPath: java.lang.String, $tsCfgValidator: $TsCfgValidator",
249
        "",
250
        ""
251
      )
252

253
    val fullClassName = classNamesPrefix.reverse.mkString + className
2✔
254
    val objectString = {
255
      s"""object $className {$innerClassesStr
256
         |  def apply($ctorParams): $fullClassName = {
257
         |    $errHandlingDecl$fullClassName(
258
         |      $objectMembersStr
259
         |    )$errHandlingDispatch
260
         |  }$elemAccessorsStr
261
         |$rootAuxClasses}
262
      """.stripMargin
2✔
263
    }
264

265
    val baseType = classNamesPrefix.reverse.mkString + className
2✔
266
    Res(
2✔
267
      ot,
268
      scalaType = BaseScalaType(baseType),
2✔
269
      definition = classStr + objectString
2✔
270
    )
271
  }
272

273
  private def buildParentClassString(
274
      annTypeForParentClassName: Option[AnnType],
275
      parentClassMemberResults: Seq[(String, Res, AnnType, Boolean)]
276
  ) =
277
    annTypeForParentClassName.flatMap(_.nameIsImplementsIsExternal) match {
2✔
278
      case Some((parentClassName, _, _)) =>
2✔
279
        val superClassFieldString =
280
          if (parentClassMemberResults.nonEmpty)
2✔
281
            "(" + parentClassMemberResults.map(_._1).mkString(",") + ")"
2✔
282
          else ""
2✔
283
        s" extends $parentClassName$superClassFieldString"
284
      case None => ""
2✔
285
    }
286

287
  private def generateForAbstractObj(
288
      aot: AbstractObjectType,
289
      classNamesPrefix: List[String],
290
      className: String,
291
      annTypeForParentClassName: Option[AnnType] = None,
292
      parentClassMembers: Option[Map[String, model.AnnType]] = None
293
  ): Res = {
294

295
    genResults = genResults.copy(classNames = genResults.classNames + className)
2✔
296

297
    implicit val symbols: List[String] = aot.members.keys.toList.sorted
2✔
298
    symbols.foreach(checkUserSymbol)
2✔
299

300
    val results = symbols.map { symbol =>
2✔
301
      val a = aot.members(symbol)
2✔
302
      val res = generate(
2✔
303
        a.t,
2✔
304
        classNamesPrefix = className + "." :: classNamesPrefix,
2✔
305
        className = getClassName(symbol),
2✔
306
        Some(a),
2✔
307
        a.parentClassMembers,
2✔
308
      )
309
      (symbol, res, a, false)
2✔
310
    }
311

312
    /* Consider parent abstract classes */
313
    val parentClassMemberResults = parentClassMembers
314
      .map(_.keys.toList.sorted)
2✔
315
      .map(parentSymbols => {
316
        parentSymbols.foreach(checkUserSymbol)
2✔
317
        parentSymbols.map { symbol =>
2✔
318
          val a = parentClassMembers.get(symbol)
2✔
319
          val res = generate(
2✔
320
            a.t,
2✔
321
            classNamesPrefix = className + "." :: classNamesPrefix,
2✔
322
            className = getClassName(symbol),
2✔
323
            Some(a),
2✔
324
            a.parentClassMembers,
2✔
325
          )
326
          (symbol, res, a, true)
2✔
327
        }
328
      })
329
      .getOrElse(List.empty)
2✔
330

331
    val abstractClassMembersStr = buildClassMembersString(
2✔
332
      parentClassMemberResults ++ results,
2✔
333
      padId,
2✔
334
      isAbstractClass = true
2✔
335
    )
336

337
    val parentClassString =
338
      buildParentClassString(
2✔
339
        annTypeForParentClassName,
340
        parentClassMemberResults
341
      )
342

343
    val abstractClassStr =
344
      s"""sealed abstract class $className (
345
         | $abstractClassMembersStr
346
         |)$parentClassString
347
         |""".stripMargin
2✔
348

349
    val baseType = classNamesPrefix.reverse.mkString + className
2✔
350

351
    Res(aot, scalaType = BaseScalaType(baseType), definition = abstractClassStr)
2✔
352

353
  }
354

355
  private def generateForObjRef(
356
      ort: ObjectRefType,
357
      classNamesPrefix: List[String]
358
  ): Res = {
359

360
    val className = getClassName(ort.simpleName)
2✔
361
    genResults = genResults.copy(classNames = genResults.classNames + className)
2✔
362

363
    val fullScalaName = getClassNameForObjectRefType(ort)
2✔
364

365
    Res(ort, scalaType = BaseScalaType(fullScalaName + dbg("<X>")))
2✔
366
  }
367

368
  private def getClassNameForObjectRefType(ot: ObjectRefType): String = {
369
    val className = getClassName(ot.simpleName)
2✔
370
    val namespace = rootNamespace.resolve(ot.namespace)
2✔
371
    val fullScalaName =
372
      if (namespace.isRoot)
2✔
373
        s"${genOpts.className}.$className"
2✔
374
      else
375
        (namespace.getPath.map(getClassName) ++ Seq(className)).mkString(".")
1✔
376

377
    scribe.debug(
2✔
378
      s"getClassNameForObjectRefType:" +
379
        s" simpleName=${ot.simpleName}" +
×
380
        s" className=$className fullScalaName=$fullScalaName"
381
    )
382

383
    fullScalaName
384
  }
385

386
  private def generateForList(
387
      lt: ListType,
388
      classNamesPrefix: List[String],
389
      className: String
390
  ): Res = {
391
    val className2 =
392
      className + (if (className.endsWith("$Elm")) "" else "$Elm")
2✔
393
    val elem = generate(lt.t, classNamesPrefix, className2)
2✔
394
    Res(
2✔
395
      lt,
396
      scalaType = ListScalaType(elem.scalaType),
2✔
397
      definition = elem.definition
2✔
398
    )
399
  }
400

401
  private def generateForBasic(b: BasicType): Res = {
402
    Res(
2✔
403
      b,
404
      scalaType = BaseScalaType(name = b match {
2✔
405
        case STRING  => "java.lang.String"
2✔
406
        case INTEGER => "scala.Int"
2✔
407
        case LONG    => "scala.Long"
2✔
408
        case DOUBLE  => "scala.Double"
2✔
409
        case BOOLEAN => "scala.Boolean"
2✔
410
        case SIZE    => "scala.Long"
2✔
411
        case DURATION(_) =>
412
          if (genOpts.useDurations) "java.time.Duration" else "scala.Long"
2✔
413
      })
414
    )
415
  }
416

417
  private def generateForEnum(
418
      et: EnumObjectType,
419
      classNamesPrefix: List[String] = List.empty,
420
      className: String,
421
  ): Res = {
422
    scribe.debug(
1✔
423
      s"generateForEnum: className=$className classNamesPrefix=$classNamesPrefix"
424
    )
425
    genResults = genResults.copy(classNames = genResults.classNames + className)
1✔
426

427
    // / Example:
428
    //  sealed trait FruitType
429
    //  object FruitType {
430
    //    object apple extends FruitType
431
    //    object banana extends FruitType
432
    //    object pineapple extends FruitType
433
    //    def $resEnum(name: java.lang.String, path: java.lang.String, $tsCfgValidator: $TsCfgValidator): FruitType = name match {
434
    //      case "apple" => FruitType.apple
435
    //      case "banana" => FruitType.banana
436
    //      case "pineapple" => FruitType.pineapple
437
    //      case v => $tsCfgValidator.addInvalidEnumValue(path, v, "FruitType")
438
    //                null
439
    //    }
440
    //  }
441

442
    val resolve = {
443
      val members =
444
        et.members.map(m => s"""case "$m" => $className.$m""").mkString("\n  ")
1✔
445
      s"""def $$resEnum(name: java.lang.String, path: java.lang.String, $$tsCfgValidator: $$TsCfgValidator): $className = name match {
446
         |  $members
447
         |  case v => $$tsCfgValidator.addInvalidEnumValue(path, v, "$className")
448
         |            null
449
         |}""".stripMargin
1✔
450
    }
451

452
    val str = {
453
      val membersStr =
454
        et.members.map(m => s"object $m extends $className").mkString("\n  ")
1✔
455

456
      s"""|sealed trait $className
457
          |object $className {
458
          |  $membersStr
459
          |  ${resolve.replaceAll("\n", "\n  ")}
460
          |}""".stripMargin
1✔
461
    }
462

463
    val baseType = classNamesPrefix.reverse.mkString + className
1✔
464
    Res(et, scalaType = BaseScalaType(baseType + dbg("<X>")), definition = str)
1✔
465
  }
466
}
467

468
object ScalaGen {
469

470
  import tscfg.util
471

472
  import _root_.java.io.{File, FileWriter, PrintWriter}
473

474
  // $COVERAGE-OFF$
475
  def generate(
476
      filename: String,
477
      j7: Boolean = false,
478
      assumeAllRequired: Boolean = false,
479
      showOut: Boolean = false,
480
      s12: Boolean = false,
481
      useDurations: Boolean = false,
482
      useBackticks: Boolean = false
483
  ): GenResult = {
484
    val file      = new File("src/main/tscfg/" + filename)
485
    val source    = io.Source.fromFile(file)
486
    val sourceStr = source.mkString.trim
487
    source.close()
488

489
    if (showOut)
490
      println("source:\n  |" + sourceStr.replaceAll("\n", "\n  |"))
491

492
    val className = "Scala" + {
493
      val noPath = filename.substring(filename.lastIndexOf('/') + 1)
494
      val noDef  = noPath.replaceAll("""^def\.""", "")
495
      val symbol = noDef.substring(0, noDef.indexOf('.'))
496
      util.upperFirst(symbol) + "Cfg"
497
    }
498

499
    val rootNamespace = new NamespaceMan
500

501
    val buildResult =
502
      ModelBuilder(
503
        rootNamespace,
504
        sourceStr,
505
        assumeAllRequired = assumeAllRequired
506
      )
507
    val objectType = buildResult.objectType
508
    if (showOut) {
509
      println(
510
        "\nobjectType:\n  |" + model.util
511
          .format(objectType)
512
          .replaceAll("\n", "\n  |")
513
      )
514
      if (buildResult.warnings.nonEmpty) {
515
        println("warnings:")
516
        buildResult.warnings.foreach(w =>
517
          println(s"   line ${w.line}: ${w.source}: ${w.message}")
518
        )
519
      }
520
    }
521

522
    val genOpts = GenOpts(
523
      "tscfg.example",
524
      className,
525
      j7 = j7,
526
      useBackticks = useBackticks,
527
      useDurations = useDurations,
528
      s12 = s12
529
    )
530

531
    val generator = new ScalaGen(genOpts, rootNamespace)
532

533
    val results = generator.generate(objectType)
534

535
    val destFilename = s"src/test/scala/tscfg/example/$className.scala"
536
    val destFile     = new File(destFilename)
537
    val out          = new PrintWriter(new FileWriter(destFile), true)
538
    out.println(results.code)
539
    out.close()
540
    results
541
  }
542

543
  def main(args: Array[String]): Unit = {
544
    val filename = args.headOption.getOrElse("example/example.conf")
545
    val results  = generate(filename, showOut = true)
546
    println(s"""classNames: ${results.classNames}
547
         |fields    : ${results.fields}
548
      """.stripMargin)
549
  }
550

551
  // $COVERAGE-ON$
552
}
553

554
private[scala] object defs {
555

556
  abstract sealed class ScalaType
557

558
  case class BaseScalaType(name: String) extends ScalaType {
559
    override def toString: String = name
2✔
560
  }
561

562
  case class ListScalaType(st: ScalaType) extends ScalaType {
563
    override def toString: String = s"scala.List[$st]"
564
  }
565

566
  case class Res(typ: Type, scalaType: ScalaType, definition: String = "")
567

568
}
569

570
private[scala] case class MethodNames() {
571
  val strA        = "$_str"
2✔
572
  val intA        = "$_int"
2✔
573
  val lngA        = "$_lng"
2✔
574
  val dblA        = "$_dbl"
2✔
575
  val blnA        = "$_bln"
2✔
576
  val durA        = "$_dur"
2✔
577
  val sizA        = "$_siz"
2✔
578
  val expE        = "$_expE"
2✔
579
  val listPrefix  = "$_L"
2✔
580
  val requireName = "$_require"
2✔
581

582
  def checkUserSymbol(symbol: String): Unit = {
583
    if (symbol.startsWith("$_"))
2✔
584
      println(
1✔
585
        s"""
586
           |WARNING: Symbol $symbol may cause conflict with generated code.
587
           |         Avoid the $$_ prefix in your spec's identifiers.
588
         """.stripMargin
1✔
589
      )
590
  }
591

592
  // definition of methods used to access list's elements of basic type
593
  val basicElemAccessDefinition: Map[String, String] = {
594
    List(strA, intA, lngA, dblA, blnA, sizA)
2✔
595
      .map(k => k -> scalaDef(k))
2✔
596
      .toMap
2✔
597
  }
598

599
  val expEDef: String = scalaDef(expE)
2✔
600

601
  val requireDef: String = scalaDef(requireName)
2✔
602
}
603

604
private[scala] case class Getter(
605
    genOpts: GenOpts,
606
    hasPath: String,
607
    accessors: Accessors
608
)(implicit val methodNames: MethodNames, val rootNamespace: NamespaceMan) {
609

610
  import defs._
611

612
  def instance(a: AnnType, res: Res, path: String)(implicit
613
      listAccessors: collection.mutable.LinkedHashMap[String, String]
614
  ): String = {
615

616
    val objRefResolution: Option[String] = a.t match {
2✔
617
      case ort: ObjectRefType => objectRefInstance(ort, res, path)
2✔
618
      case _                  => None
2✔
619
    }
620

621
    objRefResolution.getOrElse {
2✔
622
      a.t match {
2✔
623
        case bt: BasicType    => basicInstance(a, bt, path)
2✔
624
        case _: ObjectAbsType => objectInstance(a, res, path)
2✔
625
        case lt: ListType     => listInstance(a, lt, res, path)
2✔
626
      }
627
    }
628
  }
629

630
  private def objectRefInstance(
631
      ort: ObjectRefType,
632
      res: Res,
633
      path: String
634
  ): Option[String] = {
635
    val namespace = rootNamespace.resolve(ort.namespace)
2✔
636
    namespace.getDefine(ort.simpleName) flatMap { t =>
2✔
637
      t match {
638
        case _: EnumObjectType => Some(enumInstance(res, path))
1✔
639
        case _                 => None
2✔
640
      }
641
    }
642
  }
643

644
  private def enumInstance(res: Res, path: String): String = {
645
    val className = res.scalaType.toString
1✔
646

647
    // // Example:
648
    // fruit = FruitType.$resEnum(c.getString("fruit"), parentPath + "fruit", $tsCfgValidator)
649

650
    s"""$className.$$resEnum(c.getString("$path"), parentPath + "$path", $$tsCfgValidator)"""
651
  }
652

653
  private def objectInstance(a: AnnType, res: Res, path: String)(implicit
654
      listAccessors: collection.mutable.Map[String, String]
655
  ): String = {
656
    val className = res.scalaType.toString
2✔
657

658
    val ppArg = s""", parentPath + "$path.", $$tsCfgValidator"""
659

660
    def reqConfigCall = {
661
      val methodName = "$_reqConfig"
1✔
662
      listAccessors += methodName -> scalaDef(methodName)
1✔
663
      s"""$methodName(parentPath, c, "$path", $$tsCfgValidator)"""
664
    }
665

666
    if (genOpts.assumeAllRequired)
2✔
667
      s"""$className($reqConfigCall$ppArg)"""
1✔
668
    else if (a.optional) {
2✔
669
      s"""if(c.$hasPath("$path")) scala.Some($className(c.getConfig("$path")$ppArg)) else None"""
2✔
670
    }
671
    else {
672
      // TODO revisit #33 handling of object as always optional
673
      s"""$className(if(c.$hasPath("$path")) c.getConfig("$path") else com.typesafe.config.ConfigFactory.parseString("$path{}")$ppArg)"""
2✔
674
    }
675
  }
676

677
  private def listInstance(a: AnnType, lt: ListType, res: Res, path: String)(
678
      implicit listAccessors: collection.mutable.Map[String, String]
679
  ): String = {
680
    val scalaType: ListScalaType = res.scalaType.asInstanceOf[ListScalaType]
2✔
681
    val base = accessors.listMethodName(scalaType, lt, path, genOpts.s12)
2✔
682
    if (a.optional) {
2✔
683
      s"""if(c.$hasPath("$path")) scala.Some($base) else None"""
2✔
684
    }
685
    else base
2✔
686
  }
687

688
  private def basicInstance(a: AnnType, bt: BasicType, path: String)(implicit
689
      listAccessors: collection.mutable.Map[String, String]
690
  ): String = {
691
    val getter =
692
      tsConfigUtil.basicGetter(bt, path, genOpts.useDurations, forScala = true)
2✔
693

694
    a.default match {
2✔
695
      case Some(v) =>
2✔
696
        val value =
697
          tsConfigUtil.basicValue(a.t, v, useDurations = genOpts.useDurations)
2✔
698
        (bt, value) match {
699
          case (BOOLEAN, "true")  => s"""!c.$hasPath("$path") || c.$getter"""
700
          case (BOOLEAN, "false") => s"""c.$hasPath("$path") && c.$getter"""
701
          case (DURATION(qs), duration) if genOpts.useDurations =>
2✔
702
            s"""if(c.$hasPath("$path")) c.$getter else java.time.Duration.parse("$duration")"""
703
          case _ => s"""if(c.$hasPath("$path")) c.$getter else $value"""
704
        }
705

706
      case None if a.optional =>
2✔
707
        s"""if(c.$hasPath("$path")) Some(c.$getter) else None"""
2✔
708

709
      case _ =>
710
        bt match {
2✔
711
          case DURATION(_) => s"""c.$getter"""
2✔
712
          case _ =>
2✔
713
            val (methodName, methodCall) =
2✔
714
              tsConfigUtil.basicRequiredGetter(bt, path, genOpts.useDurations)
715
            listAccessors += methodName -> scalaDef(methodName)
2✔
716
            methodCall
717
        }
718
    }
719
  }
720
}
721

722
private[scala] class Accessors {
723

724
  import defs._
725

726
  val rootListAccessors: collection.mutable.LinkedHashMap[String, String] =
727
    collection.mutable.LinkedHashMap()
2✔
728

729
  def listMethodName(
730
      scalaType: ListScalaType,
731
      lt: ListType,
732
      path: String,
733
      s12: Boolean
734
  )(implicit
735
      listAccessors: collection.mutable.Map[String, String],
736
      methodNames: MethodNames,
737
      rootNamespace: NamespaceMan
738
  ): String = {
739

740
    val (_, methodName) = rec(scalaType, lt, "", s12)
2✔
741
    methodName + s"""(c.getList("$path"), parentPath, $$tsCfgValidator)"""
2✔
742
  }
743

744
  private def rec(
745
      lst: ListScalaType,
746
      lt: ListType,
747
      prefix: String,
748
      s12: Boolean
749
  )(implicit
750
      listAccessors: collection.mutable.Map[String, String],
751
      methodNames: MethodNames,
752
      rootNamespace: NamespaceMan
753
  ): (Boolean, String) = {
754

755
    val (isBasic, elemMethodName) = lst.st match {
2✔
756
      case bst: BaseScalaType =>
757
        val basic      = lt.t.isInstanceOf[BasicType]
758
        val methodName = baseName(lt.t, bst.toString)
759
        if (basic) {
760
          rootListAccessors += methodName -> methodNames
761
            .basicElemAccessDefinition(methodName)
762
          rootListAccessors += methodNames.expE -> methodNames.expEDef
763
        }
764
        (basic, methodName)
765

766
      case lst: ListScalaType =>
767
        rec(
768
          lst,
769
          lt.t.asInstanceOf[ListType],
770
          prefix + methodNames.listPrefix,
771
          s12
772
        )
773
    }
774

775
    val (methodName, methodBody) =
2✔
776
      listMethodDefinition(elemMethodName, lst.st, s12, lt)
777

778
    if (isBasic)
779
      rootListAccessors += methodName -> methodBody
2✔
780
    else
781
      listAccessors += methodName -> methodBody
2✔
782

783
    (isBasic, methodName)
2✔
784
  }
785

786
  private def baseName(t: Type, name: String)(implicit
787
      methodNames: MethodNames
788
  ): String = t match {
789
    case STRING      => methodNames.strA
2✔
790
    case INTEGER     => methodNames.intA
2✔
791
    case LONG        => methodNames.lngA
2✔
792
    case DOUBLE      => methodNames.dblA
2✔
793
    case BOOLEAN     => methodNames.blnA
2✔
794
    case SIZE        => methodNames.sizA
2✔
795
    case DURATION(_) => methodNames.durA
×
796

797
    case _: ObjectAbsType => name.replace('.', '_')
2✔
798

799
    case _: ListType => throw new AssertionError()
×
800
  }
801

802
  def listMethodDefinition(
803
      elemMethodName: String,
804
      scalaType: ScalaType,
805
      s12: Boolean,
806
      lt: ListType
807
  )(implicit
808
      methodNames: MethodNames,
809
      rootNamespace: NamespaceMan
810
  ): (String, String) = {
811

812
    val elem = if (elemMethodName.startsWith(methodNames.listPrefix)) {
2✔
813
      s"$elemMethodName(cv.asInstanceOf[com.typesafe.config.ConfigList], parentPath, $$tsCfgValidator)"
2✔
814
    }
815
    else if (elemMethodName.startsWith("$")) {
2✔
816
      s"$elemMethodName(cv)"
2✔
817
    }
818
    else {
2✔
819
      val adjusted = elemMethodName.replace("_", ".")
2✔
820
      val objRefResolution = lt.t match {
2✔
821
        case ort: ObjectRefType =>
2✔
822
          val namespace = rootNamespace.resolve(ort.namespace)
2✔
823
          namespace.getDefine(ort.simpleName) flatMap { t =>
2✔
824
            t match {
825
              case _: EnumObjectType =>
826
                // TODO some more useful path (for now just "?" below)
827
                Some(
1✔
828
                  s"""$adjusted.$$resEnum(cv.unwrapped().toString, "?", $$tsCfgValidator)"""
829
                )
830
              case _ => None
2✔
831
            }
832
          }
833

834
        case _ => None
2✔
835
      }
836
      objRefResolution.getOrElse {
2✔
837
        s"$adjusted(cv.asInstanceOf[com.typesafe.config.ConfigObject].toConfig, parentPath, $$tsCfgValidator)"
838
      }
839
    }
840

841
    val methodName = methodNames.listPrefix + elemMethodName
2✔
842
    val scalaCollectionConverter =
843
      if (s12) "scala.collection.JavaConverters._"
2✔
844
      else "scala.jdk.CollectionConverters._"
2✔
845
    val methodDef =
846
      s"""  private def $methodName(cl:com.typesafe.config.ConfigList, parentPath: java.lang.String, $$tsCfgValidator: $$TsCfgValidator): scala.List[$scalaType] = {
847
         |    import $scalaCollectionConverter
848
         |    cl.asScala.map(cv => $elem).toList
849
         |  }""".stripMargin
2✔
850
    (methodName, methodDef)
2✔
851
  }
852
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc