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(
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, Set<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 omitImplicitModifiers: Boolean = false,
242 ) = apply {
243 var a = 0
244 var deferredTypeName: ClassName? = null // used by "import static" logic
245 val partIterator = codeBlock.formatParts.listIterator()
246 while (partIterator.hasNext()) {
247 when (val part = partIterator.next()) {
248 "%L" -> emitLiteral(codeBlock.args[a++], isConstantContext, omitImplicitModifiers)
249
250 "%N" -> emit(codeBlock.args[a++] as String)
251
252 "%S" -> {
253 val string = codeBlock.args[a++] as String?
254 // Emit null as a literal null: no quotes.
255 val literal = if (string != null) {
256 stringLiteralWithQuotes(
257 string,
258 isInsideRawString = false,
259 isConstantContext = isConstantContext,
260 )
261 } else {
262 "null"
263 }
264 emit(literal, nonWrapping = true)
265 }
266
267 "%P" -> {
268 val string = codeBlock.args[a++]?.let { arg ->
269 if (arg is CodeBlock) {
270 arg.toString(this@CodeWriter)
271 } else {
272 arg as String?
273 }
274 }
275 // Emit null as a literal null: no quotes.
276 val literal = if (string != null) {
277 stringLiteralWithQuotes(
278 string,
279 isInsideRawString = true,
280 isConstantContext = isConstantContext,
281 )
282 } else {
283 "null"
284 }
285 emit(literal, nonWrapping = true)
286 }
287
288 "%T" -> {
289 var typeName = codeBlock.args[a++] as TypeName
290 if (typeName.isAnnotated) {
291 typeName.emitAnnotations(this)
292 typeName = typeName.copy(annotations = emptyList())
293 }
294 // defer "typeName.emit(this)" if next format part will be handled by the default case
295 var defer = false
296 if (typeName is ClassName && partIterator.hasNext()) {
297 if (!codeBlock.formatParts[partIterator.nextIndex()].startsWith("%")) {
298 val candidate = typeName
299 if (candidate.canonicalName in memberImportNames) {
300 check(deferredTypeName == null) { "pending type for static import?!" }
301 deferredTypeName = candidate
302 defer = true
303 }
304 }
305 }
306 if (!defer) typeName.emit(this)
307 typeName.emitNullable(this)
308 }
309
310 "%M" -> {
311 val memberName = codeBlock.args[a++] as MemberName
312 memberName.emit(this)
313 }
314
315 "%%" -> emit("%")
316
317 "⇥" -> indent()
318
319 "⇤" -> unindent()
320
321 "«" -> {
322 check(statementLine == -1) {
323 """
324 |Can't open a new statement until the current statement is closed (opening « followed
325 |by another « without a closing »).
326 |Current code block:
327 |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
328 |- Arguments: ${codeBlock.args}
329 |
330 """.trimMargin()
331 }
332 statementLine = 0
333 }
334
335 "»" -> {
336 check(statementLine != -1) {
337 """
338 |Can't close a statement that hasn't been opened (closing » is not preceded by an
339 |opening «).
340 |Current code block:
341 |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
342 |- Arguments: ${codeBlock.args}
343 |
344 """.trimMargin()
345 }
346 if (statementLine > 0) {
347 unindent(2) // End a multi-line statement. Decrease the indentation level.
348 }
349 statementLine = -1
350 }
351
352 else -> {
353 // Handle deferred type.
354 var doBreak = false
355 if (deferredTypeName != null) {
356 if (part.startsWith(".")) {
357 if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
358 // Okay, static import hit and all was emitted, so clean-up and jump to next part.
359 deferredTypeName = null
360 doBreak = true
361 }
362 }
363 if (!doBreak) {
364 deferredTypeName!!.emit(this)
365 deferredTypeName = null
366 }
367 }
368 if (!doBreak) {
369 emit(part)
370 }
371 }
372 }
373 }
374 if (ensureTrailingNewline && out.hasPendingSegments) {
375 emit("\n")
376 }
377 }
378
emitStaticImportMembernull379 private fun emitStaticImportMember(canonical: String, part: String): Boolean {
380 val partWithoutLeadingDot = part.substring(1)
381 if (partWithoutLeadingDot.isEmpty()) return false
382 val first = partWithoutLeadingDot[0]
383 if (!Character.isJavaIdentifierStart(first)) return false
384 val explicit = imports[canonical + "." + extractMemberName(partWithoutLeadingDot)]
385 if (explicit != null) {
386 if (explicit.alias != null) {
387 val memberName = extractMemberName(partWithoutLeadingDot)
388 emit(partWithoutLeadingDot.replaceFirst(memberName, explicit.alias))
389 } else {
390 emit(partWithoutLeadingDot)
391 }
392 return true
393 }
394 return false
395 }
396
emitLiteralnull397 private fun emitLiteral(o: Any?, isConstantContext: Boolean, omitImplicitModifiers: Boolean) {
398 when (o) {
399 is TypeSpec -> o.emit(this, null)
400 is AnnotationSpec -> o.emit(this, inline = true, asParameter = isConstantContext)
401 is PropertySpec -> o.emit(this, emptySet())
402 is FunSpec -> o.emit(
403 codeWriter = this,
404 enclosingName = null,
405 implicitModifiers = if (omitImplicitModifiers) emptySet() else setOf(KModifier.PUBLIC),
406 includeKdocTags = true,
407 )
408 is TypeAliasSpec -> o.emit(this)
409 is CodeBlock -> emitCode(o, isConstantContext = isConstantContext)
410 else -> emit(o.toString())
411 }
412 }
413
414 /**
415 * Returns the best name to identify `className` with in the current context. This uses the
416 * available imports and the current scope to find the shortest name available. It does not honor
417 * names visible due to inheritance.
418 */
lookupNamenull419 fun lookupName(className: ClassName): String {
420 // Find the shortest suffix of className that resolves to className. This uses both local type
421 // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
422 var nameResolved = false
423 var c: ClassName? = className
424 while (c != null) {
425 val alias = imports[c.canonicalName]?.alias
426 val simpleName = alias ?: c.simpleName
427 val resolved = resolve(simpleName)
428 nameResolved = resolved != null
429
430 // We don't care about nullability and type annotations here, as it's irrelevant for imports.
431 if (resolved == c.copy(nullable = false, annotations = emptyList())) {
432 if (alias == null) {
433 referencedNames.add(className.topLevelClassName().simpleName)
434 }
435 val nestedClassNames = className.simpleNames.subList(
436 c.simpleNames.size,
437 className.simpleNames.size,
438 ).joinToString(".")
439 return "$simpleName.$nestedClassNames"
440 }
441 c = c.enclosingClassName()
442 }
443
444 // If the name resolved but wasn't a match, we're stuck with the fully qualified name.
445 if (nameResolved) {
446 return className.canonicalName
447 }
448
449 // If the class is in the same package and there's no import alias for that class, we're done.
450 if (packageName == className.packageName && imports[className.canonicalName]?.alias == null) {
451 referencedNames.add(className.topLevelClassName().simpleName)
452 return className.simpleNames.joinToString(".")
453 }
454
455 // We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
456 if (!kdoc) {
457 importableType(className)
458 }
459
460 return className.canonicalName
461 }
462
lookupNamenull463 fun lookupName(memberName: MemberName): String {
464 val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
465 // Match an imported member.
466 val importedMembers = importedMembers[simpleName] ?: emptySet()
467 val found = memberName in importedMembers
468 if (found && !isMethodNameUsedInCurrentContext(simpleName)) {
469 return simpleName
470 } else if (importedMembers.isNotEmpty() && memberName.enclosingClassName != null) {
471 val enclosingClassName = lookupName(memberName.enclosingClassName)
472 return "$enclosingClassName.$simpleName"
473 } else if (found) {
474 return simpleName
475 }
476
477 // If the member is in the same package, we're done.
478 if (packageName == memberName.packageName && memberName.enclosingClassName == null) {
479 referencedNames.add(memberName.simpleName)
480 return memberName.simpleName
481 }
482
483 // We'll have to use the fully-qualified name.
484 // Mark the member as importable for a future pass unless the name clashes with
485 // a method in the current context
486 if (!kdoc && (
487 memberName.isExtension ||
488 !isMethodNameUsedInCurrentContext(memberName.simpleName)
489 )
490 ) {
491 importableMember(memberName)
492 }
493
494 return memberName.canonicalName
495 }
496
497 // TODO(luqasn): also honor superclass members when resolving names.
isMethodNameUsedInCurrentContextnull498 private fun isMethodNameUsedInCurrentContext(simpleName: String): Boolean {
499 for (it in typeSpecStack.reversed()) {
500 if (it.funSpecs.any { it.name == simpleName }) {
501 return true
502 }
503 if (!it.modifiers.contains(KModifier.INNER)) {
504 break
505 }
506 }
507 return false
508 }
509
importableTypenull510 private fun importableType(className: ClassName) {
511 val topLevelClassName = className.topLevelClassName()
512 val alias = imports[className.canonicalName]?.alias
513 val simpleName = alias ?: topLevelClassName.simpleName
514 // Check for name clashes with members.
515 if (simpleName !in importableMembers) {
516 // Maintain the inner class name if the alias exists.
517 val newImportTypes = if (alias == null) {
518 topLevelClassName
519 } else {
520 className
521 }
522 importableTypes[simpleName] = importableTypes.getValue(simpleName) + newImportTypes
523 }
524 }
525
importableMembernull526 private fun importableMember(memberName: MemberName) {
527 val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
528 // Check for name clashes with types.
529 if (memberName.isExtension || simpleName !in importableTypes) {
530 importableMembers[simpleName] = importableMembers.getValue(simpleName) + memberName
531 }
532 }
533
534 /**
535 * Returns the class or enum value referenced by `simpleName`, using the current nesting context and
536 * imports.
537 */
538 // TODO(jwilson): also honor superclass members when resolving names.
resolvenull539 private fun resolve(simpleName: String): ClassName? {
540 // Match a child of the current (potentially nested) class.
541 for (i in typeSpecStack.indices.reversed()) {
542 val typeSpec = typeSpecStack[i]
543 if (simpleName in typeSpec.nestedTypesSimpleNames) {
544 return stackClassName(i, simpleName)
545 }
546 }
547
548 if (typeSpecStack.size > 0) {
549 val typeSpec = typeSpecStack[0]
550 if (typeSpec.name == simpleName) {
551 // Match the top-level class.
552 return ClassName(packageName, simpleName)
553 }
554 if (typeSpec.isEnum && typeSpec.enumConstants.keys.contains(simpleName)) {
555 // Match a top level enum value.
556 // Enum values are not proper classes but can still be modeled using ClassName.
557 return ClassName(packageName, typeSpec.name!!).nestedClass(simpleName)
558 }
559 }
560
561 // Match an imported type.
562 val importedType = importedTypes[simpleName]
563 if (importedType != null) return importedType
564
565 // No match.
566 return null
567 }
568
569 /** Returns the class named `simpleName` when nested in the class at `stackDepth`. */
stackClassNamenull570 private fun stackClassName(stackDepth: Int, simpleName: String): ClassName {
571 var className = ClassName(packageName, typeSpecStack[0].name!!)
572 for (i in 1..stackDepth) {
573 className = className.nestedClass(typeSpecStack[i].name!!)
574 }
575 return className.nestedClass(simpleName)
576 }
577
578 /**
579 * Emits `s` with indentation as required. It's important that all code that writes to
580 * [CodeWriter.out] does it through here, since we emit indentation lazily in order to avoid
581 * unnecessary trailing whitespace.
582 */
<lambda>null583 fun emit(s: String, nonWrapping: Boolean = false) = apply {
584 var first = true
585 for (line in s.split('\n')) {
586 // Emit a newline character. Make sure blank lines in KDoc & comments look good.
587 if (!first) {
588 if ((kdoc || comment) && trailingNewline) {
589 emitIndentation()
590 out.appendNonWrapping(if (kdoc) " *" else "//")
591 }
592 out.newline()
593 trailingNewline = true
594 if (statementLine != -1) {
595 if (statementLine == 0) {
596 indent(2) // Begin multiple-line statement. Increase the indentation level.
597 }
598 statementLine++
599 }
600 }
601
602 first = false
603 if (line.isEmpty()) continue // Don't indent empty lines.
604
605 // Emit indentation and comment prefix if necessary.
606 if (trailingNewline) {
607 emitIndentation()
608 if (kdoc) {
609 out.appendNonWrapping(" * ")
610 } else if (comment) {
611 out.appendNonWrapping("// ")
612 }
613 }
614
615 if (nonWrapping) {
616 out.appendNonWrapping(line)
617 } else {
618 out.append(
619 line,
620 indentLevel = if (kdoc) indentLevel else indentLevel + 2,
621 linePrefix = if (kdoc) " * " else "",
622 )
623 }
624 trailingNewline = false
625 }
626 }
627
emitIndentationnull628 private fun emitIndentation() {
629 for (j in 0..<indentLevel) {
630 out.appendNonWrapping(indent)
631 }
632 }
633
634 /**
635 * Returns whether a [KModifier.PUBLIC] should be emitted.
636 *
637 * If [modifiers] contains [KModifier.PUBLIC], this method always returns `true`.
638 *
639 * Otherwise, this will return `true` when [KModifier.PUBLIC] is one of the [implicitModifiers]
640 * and there are no other opposing modifiers (like [KModifier.PROTECTED] etc.) supplied by the
641 * consumer in [modifiers].
642 */
shouldEmitPublicModifiernull643 private fun shouldEmitPublicModifier(
644 modifiers: Set<KModifier>,
645 implicitModifiers: Set<KModifier>,
646 ): Boolean {
647 if (modifiers.contains(KModifier.PUBLIC)) {
648 return true
649 }
650
651 if (implicitModifiers.contains(KModifier.PUBLIC) && modifiers.contains(KModifier.OVERRIDE)) {
652 return false
653 }
654
655 if (!implicitModifiers.contains(KModifier.PUBLIC)) {
656 return false
657 }
658
659 val hasOtherConsumerSpecifiedVisibility =
660 modifiers.containsAnyOf(KModifier.PRIVATE, KModifier.INTERNAL, KModifier.PROTECTED)
661
662 return !hasOtherConsumerSpecifiedVisibility
663 }
664
665 /**
666 * Returns the types that should have been imported for this code. If there were any simple name
667 * collisions, import aliases will be generated.
668 */
suggestedTypeImportsnull669 private fun suggestedTypeImports(): Map<String, Set<ClassName>> {
670 return importableTypes.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
671 }
672
673 /**
674 * Returns the members that should have been imported for this code. If there were any simple name
675 * collisions, import aliases will be generated.
676 */
suggestedMemberImportsnull677 private fun suggestedMemberImports(): Map<String, Set<MemberName>> {
678 return importableMembers.mapValues { it.value.toSet() }
679 }
680
681 /**
682 * Perform emitting actions on the current [CodeWriter] using a custom [Appendable]. The
683 * [CodeWriter] will continue using the old [Appendable] after this method returns.
684 */
emitIntonull685 inline fun emitInto(out: Appendable, action: CodeWriter.() -> Unit) {
686 val codeWrapper = this
687 LineWrapper(out, indent = DEFAULT_INDENT, columnLimit = Int.MAX_VALUE).use { newOut ->
688 val oldOut = codeWrapper.out
689 codeWrapper.out = newOut
690 @Suppress("UNUSED_EXPRESSION", "unused")
691 action()
692 codeWrapper.out = oldOut
693 }
694 }
695
closenull696 override fun close() {
697 out.close()
698 }
699
700 companion object {
701 /**
702 * Makes a pass to collect imports by executing [emitStep], and returns an instance of
703 * [CodeWriter] pre-initialized with collected imports.
704 */
withCollectedImportsnull705 fun withCollectedImports(
706 out: Appendable,
707 indent: String,
708 memberImports: Map<String, Import>,
709 emitStep: (importsCollector: CodeWriter) -> Unit,
710 ): CodeWriter {
711 // First pass: emit the entire class, just to collect the types we'll need to import.
712 val importsCollector = CodeWriter(
713 NullAppendable,
714 indent,
715 memberImports,
716 columnLimit = Integer.MAX_VALUE,
717 )
718 emitStep(importsCollector)
719 val generatedImports = mutableMapOf<String, Import>()
720 val importedTypes = importsCollector.suggestedTypeImports()
721 .generateImports(
722 generatedImports,
723 computeCanonicalName = ClassName::canonicalName,
724 capitalizeAliases = true,
725 referencedNames = importsCollector.referencedNames,
726 )
727 val importedMembers = importsCollector.suggestedMemberImports()
728 .generateImports(
729 generatedImports,
730 computeCanonicalName = MemberName::canonicalName,
731 capitalizeAliases = false,
732 referencedNames = importsCollector.referencedNames,
733 )
734 importsCollector.close()
735
736 return CodeWriter(
737 out = out,
738 indent = indent,
739 imports = memberImports + generatedImports.filterKeys { it !in memberImports },
740 importedTypes = importedTypes.mapValues { it.value.single() },
741 importedMembers = importedMembers,
742 )
743 }
744
generateImportsnull745 private fun <T> Map<String, Set<T>>.generateImports(
746 generatedImports: MutableMap<String, Import>,
747 computeCanonicalName: T.() -> String,
748 capitalizeAliases: Boolean,
749 referencedNames: Set<String>,
750 ): Map<String, Set<T>> {
751 val imported = mutableMapOf<String, Set<T>>()
752 forEach { (simpleName, qualifiedNames) ->
753 val canonicalNamesToQualifiedNames = qualifiedNames.associateBy { it.computeCanonicalName() }
754 if (canonicalNamesToQualifiedNames.size == 1 && simpleName !in referencedNames) {
755 val canonicalName = canonicalNamesToQualifiedNames.keys.single()
756 generatedImports[canonicalName] = Import(canonicalName)
757
758 // For types, qualifiedNames should consist of a single name, for which an import will be generated. For
759 // members, there can be more than one qualified name mapping to a single simple name, e.g. overloaded
760 // functions declared in the same package. In these cases, a single import will suffice for all of them.
761 imported[simpleName] = qualifiedNames
762 } else {
763 generateImportAliases(simpleName, canonicalNamesToQualifiedNames, capitalizeAliases)
764 .onEach { (a, qualifiedName) ->
765 val alias = a.escapeAsAlias()
766 val canonicalName = qualifiedName.computeCanonicalName()
767 generatedImports[canonicalName] = Import(canonicalName, alias)
768
769 imported[alias] = setOf(qualifiedName)
770 }
771 }
772 }
773 return imported
774 }
775
generateImportAliasesnull776 private fun <T> generateImportAliases(
777 simpleName: String,
778 canonicalNamesToQualifiedNames: Map<String, T>,
779 capitalizeAliases: Boolean,
780 ): List<Pair<String, T>> {
781 val canonicalNameSegmentsToQualifiedNames = canonicalNamesToQualifiedNames.mapKeys { (canonicalName, _) ->
782 canonicalName.split('.')
783 .dropLast(1) // Last segment of the canonical name is the simple name, drop it to avoid repetition.
784 .filter { it != "Companion" }
785 .map { it.replaceFirstChar(Char::uppercaseChar) }
786 }
787 val aliasNames = mutableMapOf<String, T>()
788 var segmentsToUse = 0
789 // Iterate until we have unique aliases for all names.
790 while (aliasNames.size != canonicalNamesToQualifiedNames.size) {
791 segmentsToUse += 1
792 aliasNames.clear()
793 for ((segments, qualifiedName) in canonicalNameSegmentsToQualifiedNames) {
794 val aliasPrefix = segments.takeLast(min(segmentsToUse, segments.size))
795 .joinToString(separator = "")
796 .replaceFirstChar { if (!capitalizeAliases) it.lowercaseChar() else it }
797 val aliasName = aliasPrefix + simpleName.replaceFirstChar(Char::uppercaseChar)
798 aliasNames[aliasName] = qualifiedName
799 }
800 }
801 return aliasNames.toList()
802 }
803 }
804 }
805