1 /*
<lambda>null2 * Copyright (C) 2015 Square, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.squareup.kotlinpoet
17
18 import java.io.Closeable
19 import kotlin.math.min
20
21 /** Sentinel value that indicates that no user-provided package has been set. */
22 private val NO_PACKAGE = String()
23
24 internal val NULLABLE_ANY = ANY.copy(nullable = true)
25
26 private fun extractMemberName(part: String): String {
27 require(Character.isJavaIdentifierStart(part[0])) { "not an identifier: $part" }
28 for (i in 1..part.length) {
29 if (!part.substring(0, i).isIdentifier) {
30 return part.substring(0, i - 1)
31 }
32 }
33 return part
34 }
35
buildCodeStringnull36 internal inline fun buildCodeString(builderAction: CodeWriter.() -> Unit): String {
37 val stringBuilder = StringBuilder()
38 CodeWriter(stringBuilder, columnLimit = Integer.MAX_VALUE).use {
39 it.builderAction()
40 }
41 return stringBuilder.toString()
42 }
43
buildCodeStringnull44 internal fun buildCodeString(
45 codeWriter: CodeWriter,
46 builderAction: CodeWriter.() -> Unit,
47 ): String {
48 val stringBuilder = StringBuilder()
49 codeWriter.emitInto(stringBuilder, builderAction)
50 return stringBuilder.toString()
51 }
52
53 /**
54 * Converts a [FileSpec] to a string suitable to both human- and kotlinc-consumption. This honors
55 * imports, indentation, and deferred variable names.
56 */
57 internal class CodeWriter constructor(
58 out: Appendable,
59 private val indent: String = DEFAULT_INDENT,
60 imports: Map<String, Import> = emptyMap(),
61 private val importedTypes: Map<String, ClassName> = emptyMap(),
62 private val importedMembers: Map<String, MemberName> = emptyMap(),
63 columnLimit: Int = 100,
64 ) : Closeable {
65 private var out = LineWrapper(out, indent, columnLimit)
66 private var indentLevel = 0
67
68 private var kdoc = false
69 private var comment = false
70 private var packageName = NO_PACKAGE
71 private val typeSpecStack = mutableListOf<TypeSpec>()
72 private val memberImportNames = mutableSetOf<String>()
<lambda>null73 private val importableTypes = mutableMapOf<String, List<ClassName>>().withDefault { emptyList() }
<lambda>null74 private val importableMembers = mutableMapOf<String, List<MemberName>>().withDefault { emptyList() }
75 private val referencedNames = mutableSetOf<String>()
76 private var trailingNewline = false
77
<lambda>null78 val imports = imports.also {
79 for ((memberName, _) in imports) {
80 val lastDotIndex = memberName.lastIndexOf('.')
81 if (lastDotIndex >= 0) {
82 memberImportNames.add(memberName.substring(0, lastDotIndex))
83 }
84 }
85 }
86
87 /**
88 * When emitting a statement, this is the line of the statement currently being written. The first
89 * line of a statement is indented normally and subsequent wrapped lines are double-indented. This
90 * is -1 when the currently-written line isn't part of a statement.
91 */
92 var statementLine = -1
93
<lambda>null94 fun indent(levels: Int = 1) = apply {
95 indentLevel += levels
96 }
97
<lambda>null98 fun unindent(levels: Int = 1) = apply {
99 require(indentLevel - levels >= 0) { "cannot unindent $levels from $indentLevel" }
100 indentLevel -= levels
101 }
102
<lambda>null103 fun pushPackage(packageName: String) = apply {
104 check(this.packageName === NO_PACKAGE) { "package already set: ${this.packageName}" }
105 this.packageName = packageName
106 }
107
<lambda>null108 fun popPackage() = apply {
109 check(packageName !== NO_PACKAGE) { "package already set: $packageName" }
110 packageName = NO_PACKAGE
111 }
112
<lambda>null113 fun pushType(type: TypeSpec) = apply {
114 this.typeSpecStack.add(type)
115 }
116
<lambda>null117 fun popType() = apply {
118 this.typeSpecStack.removeAt(typeSpecStack.size - 1)
119 }
120
emitCommentnull121 fun emitComment(codeBlock: CodeBlock) {
122 trailingNewline = true // Force the '//' prefix for the comment.
123 comment = true
124 try {
125 emitCode(codeBlock)
126 emit("\n")
127 } finally {
128 comment = false
129 }
130 }
131
emitKdocnull132 fun emitKdoc(kdocCodeBlock: CodeBlock) {
133 if (kdocCodeBlock.isEmpty()) return
134
135 emit("/**\n")
136 kdoc = true
137 try {
138 emitCode(kdocCodeBlock, ensureTrailingNewline = true)
139 } finally {
140 kdoc = false
141 }
142 emit(" */\n")
143 }
144
emitAnnotationsnull145 fun emitAnnotations(annotations: List<AnnotationSpec>, inline: Boolean) {
146 for (annotationSpec in annotations) {
147 annotationSpec.emit(this, inline)
148 emit(if (inline) " " else "\n")
149 }
150 }
151
152 /**
153 * Emits `modifiers` in the standard order. Modifiers in `implicitModifiers` will not
154 * be emitted except for [KModifier.PUBLIC]
155 */
emitModifiersnull156 fun emitModifiers(
157 modifiers: Set<KModifier>,
158 implicitModifiers: Set<KModifier> = emptySet(),
159 ) {
160 if (shouldEmitPublicModifier(modifiers, implicitModifiers)) {
161 emit(KModifier.PUBLIC.keyword)
162 emit(" ")
163 }
164 val uniqueNonPublicExplicitOnlyModifiers =
165 modifiers
166 .filterNot { it == KModifier.PUBLIC }
167 .filterNot { implicitModifiers.contains(it) }
168 .toEnumSet()
169 for (modifier in uniqueNonPublicExplicitOnlyModifiers) {
170 emit(modifier.keyword)
171 emit(" ")
172 }
173 }
174
175 /**
176 * Emits the `context` block for [contextReceivers].
177 */
emitContextReceiversnull178 fun emitContextReceivers(contextReceivers: List<TypeName>, suffix: String = "") {
179 if (contextReceivers.isNotEmpty()) {
180 val receivers = contextReceivers
181 .map { CodeBlock.of("%T", it) }
182 .joinToCode(prefix = "context(", suffix = ")")
183 emitCode(receivers)
184 emit(suffix)
185 }
186 }
187
188 /**
189 * Emit type variables with their bounds. If a type variable has more than a single bound - call
190 * [emitWhereBlock] with same input to produce an additional `where` block.
191 *
192 * This should only be used when declaring type variables; everywhere else bounds are omitted.
193 */
emitTypeVariablesnull194 fun emitTypeVariables(typeVariables: List<TypeVariableName>) {
195 if (typeVariables.isEmpty()) return
196
197 emit("<")
198 typeVariables.forEachIndexed { index, typeVariable ->
199 if (index > 0) emit(", ")
200 if (typeVariable.variance != null) {
201 emit("${typeVariable.variance.keyword} ")
202 }
203 if (typeVariable.isReified) {
204 emit("reified ")
205 }
206 emitCode("%L", typeVariable.name)
207 if (typeVariable.bounds.size == 1 && typeVariable.bounds[0] != NULLABLE_ANY) {
208 emitCode(" : %T", typeVariable.bounds[0])
209 }
210 }
211 emit(">")
212 }
213
214 /**
215 * Emit a `where` block containing type bounds for each type variable that has at least two
216 * bounds.
217 */
emitWhereBlocknull218 fun emitWhereBlock(typeVariables: List<TypeVariableName>) {
219 if (typeVariables.isEmpty()) return
220
221 var firstBound = true
222 for (typeVariable in typeVariables) {
223 if (typeVariable.bounds.size > 1) {
224 for (bound in typeVariable.bounds) {
225 if (!firstBound) emitCode(", ") else emitCode(" where ")
226 emitCode("%L : %T", typeVariable.name, bound)
227 firstBound = false
228 }
229 }
230 }
231 }
232
emitCodenull233 fun emitCode(s: String) = emitCode(CodeBlock.of(s))
234
235 fun emitCode(format: String, vararg args: Any?) = emitCode(CodeBlock.of(format, *args))
236
237 fun emitCode(
238 codeBlock: CodeBlock,
239 isConstantContext: Boolean = false,
240 ensureTrailingNewline: Boolean = false,
241 ) = apply {
242 var a = 0
243 var deferredTypeName: ClassName? = null // used by "import static" logic
244 val partIterator = codeBlock.formatParts.listIterator()
245 while (partIterator.hasNext()) {
246 when (val part = partIterator.next()) {
247 "%L" -> emitLiteral(codeBlock.args[a++], isConstantContext)
248
249 "%N" -> emit(codeBlock.args[a++] as String)
250
251 "%S" -> {
252 val string = codeBlock.args[a++] as String?
253 // Emit null as a literal null: no quotes.
254 val literal = if (string != null) {
255 stringLiteralWithQuotes(
256 string,
257 isInsideRawString = false,
258 isConstantContext = isConstantContext,
259 )
260 } else {
261 "null"
262 }
263 emit(literal, nonWrapping = true)
264 }
265
266 "%P" -> {
267 val string = codeBlock.args[a++]?.let { arg ->
268 if (arg is CodeBlock) {
269 arg.toString(this@CodeWriter)
270 } else {
271 arg as String?
272 }
273 }
274 // Emit null as a literal null: no quotes.
275 val literal = if (string != null) {
276 stringLiteralWithQuotes(
277 string,
278 isInsideRawString = true,
279 isConstantContext = isConstantContext,
280 )
281 } else {
282 "null"
283 }
284 emit(literal, nonWrapping = true)
285 }
286
287 "%T" -> {
288 var typeName = codeBlock.args[a++] as TypeName
289 if (typeName.isAnnotated) {
290 typeName.emitAnnotations(this)
291 typeName = typeName.copy(annotations = emptyList())
292 }
293 // defer "typeName.emit(this)" if next format part will be handled by the default case
294 var defer = false
295 if (typeName is ClassName && partIterator.hasNext()) {
296 if (!codeBlock.formatParts[partIterator.nextIndex()].startsWith("%")) {
297 val candidate = typeName
298 if (candidate.canonicalName in memberImportNames) {
299 check(deferredTypeName == null) { "pending type for static import?!" }
300 deferredTypeName = candidate
301 defer = true
302 }
303 }
304 }
305 if (!defer) typeName.emit(this)
306 typeName.emitNullable(this)
307 }
308
309 "%M" -> {
310 val memberName = codeBlock.args[a++] as MemberName
311 memberName.emit(this)
312 }
313
314 "%%" -> emit("%")
315
316 "⇥" -> indent()
317
318 "⇤" -> unindent()
319
320 "«" -> {
321 check(statementLine == -1) {
322 """
323 |Can't open a new statement until the current statement is closed (opening « followed
324 |by another « without a closing »).
325 |Current code block:
326 |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
327 |- Arguments: ${codeBlock.args}
328 |
329 """.trimMargin()
330 }
331 statementLine = 0
332 }
333
334 "»" -> {
335 check(statementLine != -1) {
336 """
337 |Can't close a statement that hasn't been opened (closing » is not preceded by an
338 |opening «).
339 |Current code block:
340 |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
341 |- Arguments: ${codeBlock.args}
342 |
343 """.trimMargin()
344 }
345 if (statementLine > 0) {
346 unindent(2) // End a multi-line statement. Decrease the indentation level.
347 }
348 statementLine = -1
349 }
350
351 else -> {
352 // Handle deferred type.
353 var doBreak = false
354 if (deferredTypeName != null) {
355 if (part.startsWith(".")) {
356 if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
357 // Okay, static import hit and all was emitted, so clean-up and jump to next part.
358 deferredTypeName = null
359 doBreak = true
360 }
361 }
362 if (!doBreak) {
363 deferredTypeName!!.emit(this)
364 deferredTypeName = null
365 }
366 }
367 if (!doBreak) {
368 emit(part)
369 }
370 }
371 }
372 }
373 if (ensureTrailingNewline && out.hasPendingSegments) {
374 emit("\n")
375 }
376 }
377
emitStaticImportMembernull378 private fun emitStaticImportMember(canonical: String, part: String): Boolean {
379 val partWithoutLeadingDot = part.substring(1)
380 if (partWithoutLeadingDot.isEmpty()) return false
381 val first = partWithoutLeadingDot[0]
382 if (!Character.isJavaIdentifierStart(first)) return false
383 val explicit = imports[canonical + "." + extractMemberName(partWithoutLeadingDot)]
384 if (explicit != null) {
385 if (explicit.alias != null) {
386 val memberName = extractMemberName(partWithoutLeadingDot)
387 emit(partWithoutLeadingDot.replaceFirst(memberName, explicit.alias))
388 } else {
389 emit(partWithoutLeadingDot)
390 }
391 return true
392 }
393 return false
394 }
395
emitLiteralnull396 private fun emitLiteral(o: Any?, isConstantContext: Boolean) {
397 when (o) {
398 is TypeSpec -> o.emit(this, null)
399 is AnnotationSpec -> o.emit(this, inline = true, asParameter = isConstantContext)
400 is PropertySpec -> o.emit(this, emptySet())
401 is FunSpec -> o.emit(
402 codeWriter = this,
403 enclosingName = null,
404 implicitModifiers = setOf(KModifier.PUBLIC),
405 includeKdocTags = true,
406 )
407 is TypeAliasSpec -> o.emit(this)
408 is CodeBlock -> emitCode(o, isConstantContext = isConstantContext)
409 else -> emit(o.toString())
410 }
411 }
412
413 /**
414 * Returns the best name to identify `className` with in the current context. This uses the
415 * available imports and the current scope to find the shortest name available. It does not honor
416 * names visible due to inheritance.
417 */
lookupNamenull418 fun lookupName(className: ClassName): String {
419 // Find the shortest suffix of className that resolves to className. This uses both local type
420 // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
421 var nameResolved = false
422 var c: ClassName? = className
423 while (c != null) {
424 val alias = imports[c.canonicalName]?.alias
425 val simpleName = alias ?: c.simpleName
426 val resolved = resolve(simpleName)
427 nameResolved = resolved != null
428
429 // We don't care about nullability and type annotations here, as it's irrelevant for imports.
430 if (resolved == c.copy(nullable = false, annotations = emptyList())) {
431 if (alias == null) {
432 referencedNames.add(className.topLevelClassName().simpleName)
433 }
434 val nestedClassNames = className.simpleNames.subList(
435 c.simpleNames.size,
436 className.simpleNames.size,
437 ).joinToString(".")
438 return "$simpleName.$nestedClassNames"
439 }
440 c = c.enclosingClassName()
441 }
442
443 // If the name resolved but wasn't a match, we're stuck with the fully qualified name.
444 if (nameResolved) {
445 return className.canonicalName
446 }
447
448 // If the class is in the same package and there's no import alias for that class, we're done.
449 if (packageName == className.packageName && imports[className.canonicalName]?.alias == null) {
450 referencedNames.add(className.topLevelClassName().simpleName)
451 return className.simpleNames.joinToString(".")
452 }
453
454 // We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
455 if (!kdoc) {
456 importableType(className)
457 }
458
459 return className.canonicalName
460 }
461
lookupNamenull462 fun lookupName(memberName: MemberName): String {
463 val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
464 // Match an imported member.
465 val importedMember = importedMembers[simpleName]
466 if (importedMember == memberName) {
467 return simpleName
468 } else if (importedMember != null && memberName.enclosingClassName != null) {
469 val enclosingClassName = lookupName(memberName.enclosingClassName)
470 return "$enclosingClassName.$simpleName"
471 }
472
473 // If the member is in the same package, we're done.
474 if (packageName == memberName.packageName && memberName.enclosingClassName == null) {
475 referencedNames.add(memberName.simpleName)
476 return memberName.simpleName
477 }
478
479 // We'll have to use the fully-qualified name.
480 // Mark the member as importable for a future pass unless the name clashes with
481 // a method in the current context
482 if (!kdoc && (
483 memberName.isExtension ||
484 !isMethodNameUsedInCurrentContext(memberName.simpleName)
485 )
486 ) {
487 importableMember(memberName)
488 }
489
490 return memberName.canonicalName
491 }
492
493 // TODO(luqasn): also honor superclass members when resolving names.
isMethodNameUsedInCurrentContextnull494 private fun isMethodNameUsedInCurrentContext(simpleName: String): Boolean {
495 for (it in typeSpecStack.reversed()) {
496 if (it.funSpecs.any { it.name == simpleName }) {
497 return true
498 }
499 if (!it.modifiers.contains(KModifier.INNER)) {
500 break
501 }
502 }
503 return false
504 }
505
importableTypenull506 private fun importableType(className: ClassName) {
507 val topLevelClassName = className.topLevelClassName()
508 val simpleName = imports[className.canonicalName]?.alias ?: topLevelClassName.simpleName
509 // Check for name clashes with members.
510 if (simpleName !in importableMembers) {
511 importableTypes[simpleName] = importableTypes.getValue(simpleName) + topLevelClassName
512 }
513 }
514
importableMembernull515 private fun importableMember(memberName: MemberName) {
516 if (memberName.packageName.isNotEmpty()) {
517 val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
518 // Check for name clashes with types.
519 if (simpleName !in importableTypes) {
520 importableMembers[simpleName] = importableMembers.getValue(simpleName) + memberName
521 }
522 }
523 }
524
525 /**
526 * Returns the class or enum value referenced by `simpleName`, using the current nesting context and
527 * imports.
528 */
529 // TODO(jwilson): also honor superclass members when resolving names.
resolvenull530 private fun resolve(simpleName: String): ClassName? {
531 // Match a child of the current (potentially nested) class.
532 for (i in typeSpecStack.indices.reversed()) {
533 val typeSpec = typeSpecStack[i]
534 if (simpleName in typeSpec.nestedTypesSimpleNames) {
535 return stackClassName(i, simpleName)
536 }
537 }
538
539 if (typeSpecStack.size > 0) {
540 val typeSpec = typeSpecStack[0]
541 if (typeSpec.name == simpleName) {
542 // Match the top-level class.
543 return ClassName(packageName, simpleName)
544 }
545 if (typeSpec.isEnum && typeSpec.enumConstants.keys.contains(simpleName)) {
546 // Match a top level enum value.
547 // Enum values are not proper classes but can still be modeled using ClassName.
548 return ClassName(packageName, typeSpec.name!!).nestedClass(simpleName)
549 }
550 }
551
552 // Match an imported type.
553 val importedType = importedTypes[simpleName]
554 if (importedType != null) return importedType
555
556 // No match.
557 return null
558 }
559
560 /** Returns the class named `simpleName` when nested in the class at `stackDepth`. */
stackClassNamenull561 private fun stackClassName(stackDepth: Int, simpleName: String): ClassName {
562 var className = ClassName(packageName, typeSpecStack[0].name!!)
563 for (i in 1..stackDepth) {
564 className = className.nestedClass(typeSpecStack[i].name!!)
565 }
566 return className.nestedClass(simpleName)
567 }
568
569 /**
570 * Emits `s` with indentation as required. It's important that all code that writes to
571 * [CodeWriter.out] does it through here, since we emit indentation lazily in order to avoid
572 * unnecessary trailing whitespace.
573 */
<lambda>null574 fun emit(s: String, nonWrapping: Boolean = false) = apply {
575 var first = true
576 for (line in s.split('\n')) {
577 // Emit a newline character. Make sure blank lines in KDoc & comments look good.
578 if (!first) {
579 if ((kdoc || comment) && trailingNewline) {
580 emitIndentation()
581 out.appendNonWrapping(if (kdoc) " *" else "//")
582 }
583 out.newline()
584 trailingNewline = true
585 if (statementLine != -1) {
586 if (statementLine == 0) {
587 indent(2) // Begin multiple-line statement. Increase the indentation level.
588 }
589 statementLine++
590 }
591 }
592
593 first = false
594 if (line.isEmpty()) continue // Don't indent empty lines.
595
596 // Emit indentation and comment prefix if necessary.
597 if (trailingNewline) {
598 emitIndentation()
599 if (kdoc) {
600 out.appendNonWrapping(" * ")
601 } else if (comment) {
602 out.appendNonWrapping("// ")
603 }
604 }
605
606 if (nonWrapping) {
607 out.appendNonWrapping(line)
608 } else {
609 out.append(
610 line,
611 indentLevel = if (kdoc) indentLevel else indentLevel + 2,
612 linePrefix = if (kdoc) " * " else "",
613 )
614 }
615 trailingNewline = false
616 }
617 }
618
emitIndentationnull619 private fun emitIndentation() {
620 for (j in 0..<indentLevel) {
621 out.appendNonWrapping(indent)
622 }
623 }
624
625 /**
626 * Returns whether a [KModifier.PUBLIC] should be emitted.
627 *
628 * If [modifiers] contains [KModifier.PUBLIC], this method always returns `true`.
629 *
630 * Otherwise, this will return `true` when [KModifier.PUBLIC] is one of the [implicitModifiers]
631 * and there are no other opposing modifiers (like [KModifier.PROTECTED] etc.) supplied by the
632 * consumer in [modifiers].
633 */
shouldEmitPublicModifiernull634 private fun shouldEmitPublicModifier(
635 modifiers: Set<KModifier>,
636 implicitModifiers: Set<KModifier>,
637 ): Boolean {
638 if (modifiers.contains(KModifier.PUBLIC)) {
639 return true
640 }
641
642 if (implicitModifiers.contains(KModifier.PUBLIC) && modifiers.contains(KModifier.OVERRIDE)) {
643 return false
644 }
645
646 if (!implicitModifiers.contains(KModifier.PUBLIC)) {
647 return false
648 }
649
650 val hasOtherConsumerSpecifiedVisibility =
651 modifiers.containsAnyOf(KModifier.PRIVATE, KModifier.INTERNAL, KModifier.PROTECTED)
652
653 return !hasOtherConsumerSpecifiedVisibility
654 }
655
656 /**
657 * Returns the types that should have been imported for this code. If there were any simple name
658 * collisions, import aliases will be generated.
659 */
suggestedTypeImportsnull660 private fun suggestedTypeImports(): Map<String, Set<ClassName>> {
661 return importableTypes.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
662 }
663
664 /**
665 * Returns the members that should have been imported for this code. If there were any simple name
666 * collisions, import aliases will be generated.
667 */
suggestedMemberImportsnull668 private fun suggestedMemberImports(): Map<String, Set<MemberName>> {
669 return importableMembers.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
670 }
671
672 /**
673 * Perform emitting actions on the current [CodeWriter] using a custom [Appendable]. The
674 * [CodeWriter] will continue using the old [Appendable] after this method returns.
675 */
emitIntonull676 inline fun emitInto(out: Appendable, action: CodeWriter.() -> Unit) {
677 val codeWrapper = this
678 LineWrapper(out, indent = DEFAULT_INDENT, columnLimit = Int.MAX_VALUE).use { newOut ->
679 val oldOut = codeWrapper.out
680 codeWrapper.out = newOut
681 action()
682 codeWrapper.out = oldOut
683 }
684 }
685
closenull686 override fun close() {
687 out.close()
688 }
689
690 companion object {
691 /**
692 * Makes a pass to collect imports by executing [emitStep], and returns an instance of
693 * [CodeWriter] pre-initialized with collected imports.
694 */
withCollectedImportsnull695 fun withCollectedImports(
696 out: Appendable,
697 indent: String,
698 memberImports: Map<String, Import>,
699 emitStep: (importsCollector: CodeWriter) -> Unit,
700 ): CodeWriter {
701 // First pass: emit the entire class, just to collect the types we'll need to import.
702 val importsCollector = CodeWriter(
703 NullAppendable,
704 indent,
705 memberImports,
706 columnLimit = Integer.MAX_VALUE,
707 )
708 emitStep(importsCollector)
709 val generatedImports = mutableMapOf<String, Import>()
710 val suggestedTypeImports = importsCollector.suggestedTypeImports()
711 .generateImports(
712 generatedImports,
713 canonicalName = ClassName::canonicalName,
714 capitalizeAliases = true,
715 )
716 val suggestedMemberImports = importsCollector.suggestedMemberImports()
717 .generateImports(
718 generatedImports,
719 canonicalName = MemberName::canonicalName,
720 capitalizeAliases = false,
721 )
722 importsCollector.close()
723
724 return CodeWriter(
725 out = out,
726 indent = indent,
727 imports = memberImports + generatedImports.filterKeys { it !in memberImports },
728 importedTypes = suggestedTypeImports,
729 importedMembers = suggestedMemberImports,
730 )
731 }
732
generateImportsnull733 private fun <T> Map<String, Set<T>>.generateImports(
734 generatedImports: MutableMap<String, Import>,
735 canonicalName: T.() -> String,
736 capitalizeAliases: Boolean,
737 ): Map<String, T> {
738 return flatMap { (simpleName, qualifiedNames) ->
739 if (qualifiedNames.size == 1) {
740 listOf(simpleName to qualifiedNames.first()).also {
741 val canonicalName = qualifiedNames.first().canonicalName()
742 generatedImports[canonicalName] = Import(canonicalName)
743 }
744 } else {
745 generateImportAliases(simpleName, qualifiedNames, canonicalName, capitalizeAliases)
746 .onEach { (alias, qualifiedName) ->
747 val canonicalName = qualifiedName.canonicalName()
748 generatedImports[canonicalName] = Import(canonicalName, alias)
749 }
750 }
751 }.toMap()
752 }
753
generateImportAliasesnull754 private fun <T> generateImportAliases(
755 simpleName: String,
756 qualifiedNames: Set<T>,
757 canonicalName: T.() -> String,
758 capitalizeAliases: Boolean,
759 ): List<Pair<String, T>> {
760 val canonicalNameSegments = qualifiedNames.associateWith { qualifiedName ->
761 qualifiedName.canonicalName().split('.')
762 .dropLast(1) // Last segment of the canonical name is the simple name, drop it to avoid repetition.
763 .filter { it != "Companion" }
764 .map { it.replaceFirstChar(Char::uppercaseChar) }
765 }
766 val aliasNames = mutableMapOf<String, T>()
767 var segmentsToUse = 0
768 // Iterate until we have unique aliases for all names.
769 while (aliasNames.size != qualifiedNames.size) {
770 segmentsToUse += 1
771 aliasNames.clear()
772 for ((qualifiedName, segments) in canonicalNameSegments) {
773 val aliasPrefix = segments.takeLast(min(segmentsToUse, segments.size))
774 .joinToString(separator = "")
775 .replaceFirstChar { if (!capitalizeAliases) it.lowercaseChar() else it }
776 val aliasName = aliasPrefix + simpleName.replaceFirstChar(Char::uppercaseChar)
777 aliasNames[aliasName] = qualifiedName
778 }
779 }
780 return aliasNames.toList()
781 }
782 }
783 }
784