/
KtLint.kt
676 lines (629 loc) · 28.2 KB
/
KtLint.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
@file:Suppress("MemberVisibilityCanBePrivate")
package com.pinterest.ktlint.core
import com.pinterest.ktlint.core.KtLint.ExperimentalParams
import com.pinterest.ktlint.core.KtLint.format
import com.pinterest.ktlint.core.KtLint.lint
import com.pinterest.ktlint.core.api.EditorConfigDefaults
import com.pinterest.ktlint.core.api.EditorConfigDefaults.Companion.EMPTY_EDITOR_CONFIG_DEFAULTS
import com.pinterest.ktlint.core.api.EditorConfigOverride
import com.pinterest.ktlint.core.api.EditorConfigOverride.Companion.EMPTY_EDITOR_CONFIG_OVERRIDE
import com.pinterest.ktlint.core.api.KtLintRuleException
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
import com.pinterest.ktlint.core.api.editorconfig.CODE_STYLE_PROPERTY
import com.pinterest.ktlint.core.api.editorconfig.CodeStyleValue
import com.pinterest.ktlint.core.api.editorconfig.DEFAULT_EDITOR_CONFIG_PROPERTIES
import com.pinterest.ktlint.core.internal.EditorConfigFinder
import com.pinterest.ktlint.core.internal.EditorConfigGenerator
import com.pinterest.ktlint.core.internal.EditorConfigLoader
import com.pinterest.ktlint.core.internal.RuleExecutionContext
import com.pinterest.ktlint.core.internal.RuleExecutionContext.Companion.createRuleExecutionContext
import com.pinterest.ktlint.core.internal.RuleExecutionException
import com.pinterest.ktlint.core.internal.RuleRunner
import com.pinterest.ktlint.core.internal.ThreadSafeEditorConfigCache.Companion.THREAD_SAFE_EDITOR_CONFIG_CACHE
import com.pinterest.ktlint.core.internal.VisitorProvider
import java.io.File
import java.nio.charset.StandardCharsets
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.absolutePathString
import mu.KotlinLogging
import org.ec4j.core.Resource
import org.ec4j.core.model.PropertyType
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
private val LOGGER = KotlinLogging.logger {}.initKtLintKLogger()
@Deprecated("Marked for removal in KtLint 0.49. See changelog or KDoc for more information.")
@Suppress("MemberVisibilityCanBePrivate")
public object KtLint {
@Deprecated(
"""
Marked for removal in KtLint 0.49.0. Use:
if (node.isRoot()) {
val fileName = (node.psi as? KtFile)?.name
...
}
""",
)
public val FILE_PATH_USER_DATA_KEY: Key<String> = Key<String>("FILE_PATH")
@Deprecated(
message = "Marked for removal in KtLint 0.49",
replaceWith = ReplaceWith("KtLintRuleEngine.Companion.STDIN_FILE"),
)
public const val STDIN_FILE: String = "<stdin>"
/**
* Parameters to invoke [KtLint.lint] and [KtLint.format] APIs.
*
* Marked for removal in KtLint 0.49. See deprecations of methods [KtLint.lint], [KtLint.format], and
* [KtLint.generateKotlinEditorConfigSection].
*
* [fileName] path of file to lint/format
* [text] Contents of file to lint/format
* [ruleProviders] a collection of [RuleProvider]s used to create new instances of [Rule]s so that it can keep
* internal state and be called thread-safe
* [userData] Map of user options. This field is deprecated and will be removed in a future version.
* [cb] callback invoked for each lint error
* [script] true if this is a Kotlin script file
* [debug] True if invoked with the --debug flag
* [editorConfigDefaults] contains default values for `.editorconfig` properties which are not set explicitly in
* any '.editorconfig' file located on the path of the [fileName]. If a property is set in [editorConfigDefaults]
* this takes precedence above the default values defined in the KtLint project.
* [editorConfigOverride] should contain entries to add/replace from loaded `.editorconfig` files. If a property is
* set in [editorConfigOverride] it takes precedence above the same property being set in any other way.
*
* For possible keys check related [Rule]s that implements [UsesEditorConfigProperties] interface.
*
* For values use `PropertyType.PropertyValue.valid("override", <expected type>)` approach.
* It is also possible to set value into "unset" state by using [PropertyType.PropertyValue.UNSET].
*
* [isInvokedFromCli] **For internal use only**: indicates that linting was invoked from KtLint CLI tool.
* Enables some internals workarounds for Kotlin Compiler initialization.
* Usually you don't need to use it and most probably it will be removed in one of next versions.
*/
@Deprecated("Marked for removal in KtLint 0.49")
public data class ExperimentalParams(
val fileName: String? = null,
val text: String,
val ruleProviders: Set<RuleProvider> = emptySet(),
val userData: Map<String, String> = emptyMap(),
val cb: (e: LintError, corrected: Boolean) -> Unit,
val script: Boolean = false,
val debug: Boolean = false,
val editorConfigDefaults: EditorConfigDefaults = EMPTY_EDITOR_CONFIG_DEFAULTS,
val editorConfigOverride: EditorConfigOverride = EMPTY_EDITOR_CONFIG_OVERRIDE,
val isInvokedFromCli: Boolean = false,
) {
internal val ruleRunners: Set<RuleRunner> =
ruleProviders
.map { RuleRunner(it) }
.distinctBy { it.ruleId }
.toSet()
internal fun getRules(): Set<Rule> =
ruleRunners
.map { it.getRule() }
.toSet()
init {
require(ruleProviders.any()) {
"A non-empty set of 'ruleProviders' need to be provided"
}
// Extract all default and custom ".editorconfig" properties which are registered
val editorConfigProperties =
getRules()
.asSequence()
.filterIsInstance<UsesEditorConfigProperties>()
.map { it.editorConfigProperties }
.flatten()
.plus(DEFAULT_EDITOR_CONFIG_PROPERTIES)
.map { it.name }
.distinct()
.toSet()
userData
.keys
.intersect(editorConfigProperties)
.let {
check(it.isEmpty()) {
"UserData should not contain '.editorconfig' properties ${it.sorted()}. Such properties " +
"should be passed via the 'ExperimentalParams.editorConfigOverride' field. Note that this is " +
"only required for properties that (potentially) contain a value that differs from the " +
"actual value in the '.editorconfig' file."
}
}
userData
.keys
.minus(editorConfigProperties)
.let {
check(it.isEmpty()) {
"UserData contains properties ${it.sorted()}. However, userData is deprecated and will be " +
"removed in a future version. Please create an issue that shows how this field is " +
"actively used."
}
}
}
internal val normalizedFilePath: Path?
get() = if (fileName == STDIN_FILE || fileName == null) {
null
} else {
Paths.get(fileName)
}
internal val isStdIn: Boolean get() = fileName == STDIN_FILE
}
/**
* Check source for lint errors.
*
* Marked for removal in KtLint 0.49. The static object [KtLint] is replaced by class [KtLintRuleEngine]. Main
* difference is that the class needs to be instantiated once with the [KtLintRuleEngineConfiguration] and is reused
* for all subsequent calls. The call to [KtLintRuleEngine.lint] only takes a reference to the code that needs to be
* linted instead of all configuration parameter as well.
*
* @throws KtLintParseException if text is not a valid Kotlin code
* @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation
*/
@Deprecated("Marked for removal in KtLint 0.49. See changelog or KDOC for migration to KtLintRuleEngine.")
public fun lint(params: ExperimentalParams) {
KtLintRuleEngine(
params.ruleProviders,
params.editorConfigDefaults,
params.editorConfigOverride,
params.isInvokedFromCli,
).lint(
Code.CodeSnippetLegacy(
content = params.text,
fileName = params.fileName,
script = params.script,
isStdIn = params.isStdIn,
),
) { params.cb(it, false) }
}
/**
* Fix style violations.
*
* Marked for removal in KtLint 0.49. The static object [KtLint] is replaced by class [KtLintRuleEngine]. Main
* difference is that the class needs to be instantiated once with the [KtLintRuleEngineConfiguration] and is reused
* for all subsequent calls. The call to [KtLintRuleEngine.format] only takes a reference to the code that needs to
* be formatted instead of all configuration parameter as well.
*
* @throws KtLintParseException if text is not a valid Kotlin code
* @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation
*/
@Deprecated("Marked for removal in KtLint 0.49. See changelog or KDOC for migration to KtLintRuleEngine.")
public fun format(params: ExperimentalParams): String =
KtLintRuleEngine(
params.ruleProviders,
params.editorConfigDefaults,
params.editorConfigOverride,
params.isInvokedFromCli,
).format(
Code.CodeSnippetLegacy(
content = params.text,
fileName = params.fileName,
script = params.script,
isStdIn = params.isStdIn,
),
) { lintError, autocorrected -> params.cb(lintError, autocorrected) }
/**
* Reduce memory usage by cleaning internal caches.
*/
@Deprecated(
message = "Marked for removal in KtLint 0.49",
replaceWith = ReplaceWith("ktLintRuleEngine.trimMemory"),
)
public fun trimMemory() {
THREAD_SAFE_EDITOR_CONFIG_CACHE.clear()
}
/**
* Get the list of files which will be accessed by KtLint when linting or formatting the given file or directory.
* The API consumer can use this list to observe changes in '.editorconfig' files. Whenever such a change is
* observed, the API consumer should call [reloadEditorConfigFile].
* To avoid unnecessary access to the file system, it is best to call this method only once for the root of the
* project which is to be [lint] or [format].
*/
@Deprecated(
message = "Marked for removal in KtLint 0.49",
replaceWith = ReplaceWith("ktLintRuleEngine.editorConfigFilePaths"),
)
public fun editorConfigFilePaths(path: Path): List<Path> =
EditorConfigFinder().findEditorConfigs(path)
/**
* Reloads an '.editorconfig' file given that it is currently loaded into the KtLint cache. This method is intended
* to be called by the API consumer when it is aware of changes in the '.editorconfig' file that should be taken
* into account with next calls to [lint] and/or [format]. See [editorConfigFilePaths] to get the list of
* '.editorconfig' files which need to be observed.
*/
@Deprecated(
message = "Marked for removal in KtLint 0.49",
replaceWith = ReplaceWith("ktLintRuleEngine.reloadEditorConfigFile"),
)
public fun reloadEditorConfigFile(path: Path) {
THREAD_SAFE_EDITOR_CONFIG_CACHE.reloadIfExists(
Resource.Resources.ofPath(path, StandardCharsets.UTF_8),
)
}
/**
* Generates Kotlin `.editorconfig` file section content based on [ExperimentalParams].
*
* Method loads merged `.editorconfig` content from [ExperimentalParams] path,
* and then, by querying rules from [ExperimentalParams] for missing properties default values,
* generates Kotlin section (default is `[*.{kt,kts}]`) new content.
*
* Rule should implement [UsesEditorConfigProperties] interface to support this.
*
* @return Kotlin section editorconfig content. For example:
* ```properties
* final-newline=true
* indent-size=4
* ```
*/
@Deprecated(
message = "Marked for removal in KtLint 0.49.",
replaceWith = ReplaceWith("ktLintRuleEngine.generateKotlinEditorConfigSection(path)"),
)
public fun generateKotlinEditorConfigSection(
params: ExperimentalParams,
): String {
val filePath = params.normalizedFilePath
requireNotNull(filePath) {
"Please pass path to existing Kotlin file"
}
return KtLintRuleEngine(
params.ruleProviders,
params.editorConfigDefaults,
params.editorConfigOverride,
params.isInvokedFromCli,
).generateKotlinEditorConfigSection(filePath)
}
}
public sealed class Code(
internal open val content: String,
internal open val fileName: String?,
internal open val filePath: Path?,
internal open val script: Boolean,
internal open val isStdIn: Boolean,
) {
/**
* A [file] containing valid Kotlin code or script. The '.editorconfig' files on the path to [file] are taken into account.
*/
public class CodeFile(
private val file: File,
) : Code(
content = file.readText(),
fileName = file.name,
filePath = file.toPath(),
script = file.name.endsWith(".kts", ignoreCase = true),
isStdIn = false,
)
/**
* The [content] represent a valid piece of Kotlin code or Kotlin script. The '.editorconfig' files on the filesystem are ignored as the
* snippet is not associated with a file path. Use [CodeFile] for scanning a file while at the same time respecting the '.editorconfig'
* files on the path to the file.
*/
public class CodeSnippet(
override val content: String,
override val script: Boolean = false,
) : Code(
content = content,
filePath = null,
fileName = null,
script = script,
isStdIn = true,
)
@Deprecated(message = "Remove in KtLint 0.49 when class ExperimentalParams is removed")
internal class CodeSnippetLegacy(
override val content: String,
override val fileName: String? = null,
override val script: Boolean = fileName?.endsWith(".kts", ignoreCase = true) == true,
override val isStdIn: Boolean = fileName == KtLintRuleEngine.STDIN_FILE,
) : Code(
content = content,
fileName = fileName,
filePath = fileName?.let { Paths.get(fileName) },
script = script,
isStdIn = isStdIn,
)
}
public class KtLintRuleEngine(
/**
* The set of [RuleProvider]s to be invoked by the [KtLintRuleEngine]. A [RuleProvider] is able to create a new instance of a [Rule] so
* that it can keep internal state and be called thread-safe manner
*/
public val ruleProviders: Set<RuleProvider> = emptySet(),
/**
* The default values for `.editorconfig` properties which are not set explicitly in any '.editorconfig' file located on the path of the
* file which is processed with the [KtLintRuleEngine]. If a property is set in [editorConfigDefaults] this takes precedence above the
* default values defined in the KtLint project.
*/
public val editorConfigDefaults: EditorConfigDefaults = EMPTY_EDITOR_CONFIG_DEFAULTS,
/**
* Override values for `.editorconfig` properties. If a property is set in [editorConfigOverride] it takes precedence above the same
* property being set in any other way.
*/
public val editorConfigOverride: EditorConfigOverride = EMPTY_EDITOR_CONFIG_OVERRIDE,
/**
* **For internal use only**: indicates that linting was invoked from KtLint CLI tool. It enables some internals workarounds for Kotlin
* Compiler initialization. This property is likely to be removed in any of next versions without further notice.
*/
public val isInvokedFromCli: Boolean = false,
) {
init {
require(ruleProviders.any()) {
"A non-empty set of 'ruleProviders' need to be provided"
}
}
internal val editorConfigLoader = EditorConfigLoader(FileSystems.getDefault())
/**
* Check [code] for lint errors. When [filePath] is provided, the '.editorconfig' files on the path are taken into
* account. For each lint violation found, the [callback] is invoked.
*
* @throws KtLintParseException if text is not a valid Kotlin code
* @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation
*/
@Deprecated(message = "Marked for removal in Ktlint 0.49")
public fun lint(
code: String,
filePath: Path? = null,
callback: (LintError) -> Unit = { },
) {
lint(
Code.CodeSnippetLegacy(
content = code,
fileName = filePath?.absolutePathString(),
),
callback,
)
}
/**
* Check the code in file [filePath] for lint errors. The '.editorconfig' files on the path to file are taken into
* account. For each lint violation found, the [callback] is invoked.
*
* @throws KtLintParseException if text is not a valid Kotlin code
* @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation
*/
@Deprecated(message = "Marked for removal in Ktlint 0.49")
public fun lint(
filePath: Path,
callback: (LintError) -> Unit = { },
) {
lint(
Code.CodeFile(filePath.toFile()),
callback,
)
}
/**
* Check the [code] for lint errors. If [code] is path as file reference then the '.editorconfig' files on the path to file are taken
* into account. For each lint violation found, the [callback] is invoked.
*
* @throws KtLintParseException if text is not a valid Kotlin code
* @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation
*/
public fun lint(
code: Code,
callback: (LintError) -> Unit = { },
) {
val ruleExecutionContext = createRuleExecutionContext(this, code)
val errors = mutableListOf<LintError>()
try {
VisitorProvider(ruleExecutionContext.ruleRunners)
.visitor(ruleExecutionContext.editorConfigProperties)
.invoke { rule, fqRuleId ->
ruleExecutionContext.executeRule(rule, fqRuleId, false) { offset, errorMessage, canBeAutoCorrected ->
val (line, col) = ruleExecutionContext.positionInTextLocator(offset)
errors.add(LintError(line, col, fqRuleId, errorMessage, canBeAutoCorrected))
}
}
} catch (e: RuleExecutionException) {
throw e.toKtLintRuleException(code.fileName)
}
errors
.sortedWith { l, r -> if (l.line != r.line) l.line - r.line else l.col - r.col }
.forEach { e -> callback(e) }
LOGGER.debug("Finished with linting file '${code.fileName}'")
}
/**
* Fix style violations in [code] for lint errors when possible. When [filePath] is provided, the '.editorconfig'
* files on the path are taken into account. For each lint violation found, the [callback] is invoked.
*
* @throws KtLintParseException if text is not a valid Kotlin code
* @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation
*/
@Deprecated(message = "Marked for removal in Ktlint 0.49")
public fun format(
code: String,
filePath: Path? = null,
callback: (LintError, Boolean) -> Unit = { _, _ -> },
): String =
format(
Code.CodeSnippetLegacy(
content = code,
fileName = filePath?.absolutePathString(),
),
callback,
)
/**
* Fix style violations in code of file [filePath] for lint errors when possible. The '.editorconfig' files on the
* path are taken into account. For each lint violation found, the [callback] is invoked.
*
* @throws KtLintParseException if text is not a valid Kotlin code
* @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation
*/
public fun format(
filePath: Path,
callback: (LintError, Boolean) -> Unit = { _, _ -> },
): String =
format(
Code.CodeFile(filePath.toFile()),
callback,
)
/**
* Fix style violations in [code] for lint errors when possible. If [code] is passed as file reference then the '.editorconfig' files on
* the path are taken into account. For each lint violation found, the [callback] is invoked.
*
* @throws KtLintParseException if text is not a valid Kotlin code
* @throws KtLintRuleException in case of internal failure caused by a bug in rule implementation
*/
public fun format(
code: Code,
callback: (LintError, Boolean) -> Unit = { _, _ -> },
): String {
val hasUTF8BOM = code.content.startsWith(UTF8_BOM)
val ruleExecutionContext = createRuleExecutionContext(this, code)
var tripped = false
var mutated = false
val errors = mutableSetOf<Pair<LintError, Boolean>>()
val ruleRunners =
ruleProviders
.map { RuleRunner(it) }
.distinctBy { it.ruleId }
.toSet()
val visitorProvider = VisitorProvider(ruleRunners)
try {
visitorProvider
.visitor(ruleExecutionContext.editorConfigProperties)
.invoke { rule, fqRuleId ->
ruleExecutionContext.executeRule(rule, fqRuleId, true) { offset, errorMessage, canBeAutoCorrected ->
tripped = true
if (canBeAutoCorrected) {
mutated = true
/**
* Rebuild the suppression locator after each change in the AST as the offsets of the
* suppression hints might have changed.
*/
ruleExecutionContext.rebuildSuppressionLocator()
}
val (line, col) = ruleExecutionContext.positionInTextLocator(offset)
errors.add(
Pair(
LintError(line, col, fqRuleId, errorMessage, canBeAutoCorrected),
// It is assumed that a rule that emits that an error can be autocorrected, also
// does correct the error.
canBeAutoCorrected,
),
)
}
}
} catch (e: RuleExecutionException) {
throw e.toKtLintRuleException(code.fileName)
}
if (tripped) {
try {
visitorProvider
.visitor(ruleExecutionContext.editorConfigProperties)
.invoke { rule, fqRuleId ->
ruleExecutionContext.executeRule(
rule,
fqRuleId,
false,
) { offset, errorMessage, canBeAutoCorrected ->
val (line, col) = ruleExecutionContext.positionInTextLocator(offset)
errors.add(
Pair(
LintError(line, col, fqRuleId, errorMessage, canBeAutoCorrected),
// It is assumed that a rule only corrects an error after it has emitted an
// error and indicating that it actually can be autocorrected.
false,
),
)
}
}
} catch (e: RuleExecutionException) {
throw e.toKtLintRuleException(code.fileName)
}
}
errors
.sortedWith { (l), (r) -> if (l.line != r.line) l.line - r.line else l.col - r.col }
.forEach { (e, corrected) -> callback(e, corrected) }
if (!mutated) {
return code.content
}
val formattedCode = ruleExecutionContext
.rootNode
.text
.replace("\n", ruleExecutionContext.determineLineSeparator(code.content))
return if (hasUTF8BOM) {
UTF8_BOM + formattedCode
} else {
formattedCode
}.also {
LOGGER.debug("Finished with formatting file '${code.fileName}'")
}
}
private fun RuleExecutionContext.determineLineSeparator(fileContent: String): String {
val eol =
editorConfigProperties["end_of_line"]
?.sourceValue
return when {
eol == "native" -> System.lineSeparator()
eol == "crlf" || eol != "lf" && fileContent.lastIndexOf('\r') != -1 -> "\r\n"
else -> "\n"
}
}
/**
* Generates Kotlin `.editorconfig` file section content based on [ExperimentalParams].
*
* Method loads merged `.editorconfig` content from [ExperimentalParams] path, and then, by querying rules from
* [KtLintRuleEngineConfiguration] for missing properties default values, generates Kotlin section (default is
* `[*.{kt,kts}]`) new content.
*
* @return Kotlin section editorconfig content. For example:
* ```properties
* final-newline=true
* indent-size=4
* ```
*/
public fun generateKotlinEditorConfigSection(filePath: Path): String {
val codeStyle =
editorConfigOverride
.properties[CODE_STYLE_PROPERTY]
?.parsed
?.safeAs<CodeStyleValue>()
?: CODE_STYLE_PROPERTY.defaultValue
val rules =
ruleProviders
.map { RuleRunner(it) }
.distinctBy { it.ruleId }
.toSet()
.map { it.getRule() }
.toSet()
return EditorConfigGenerator(this.editorConfigLoader).generateEditorconfig(
filePath,
rules,
codeStyle,
)
}
/**
* Reduce memory usage by cleaning internal caches.
*/
public fun trimMemory() {
THREAD_SAFE_EDITOR_CONFIG_CACHE.clear()
}
/**
* Get the list of files which will be accessed by KtLint when linting or formatting the given file or directory.
* The API consumer can use this list to observe changes in '.editorconfig' files. Whenever such a change is
* observed, the API consumer should call [reloadEditorConfigFile].
* To avoid unnecessary access to the file system, it is best to call this method only once for the root of the
* project which is to be [lint] or [format].
*/
public fun editorConfigFilePaths(path: Path): List<Path> =
EditorConfigFinder().findEditorConfigs(path)
/**
* Reloads an '.editorconfig' file given that it is currently loaded into the KtLint cache. This method is intended
* to be called by the API consumer when it is aware of changes in the '.editorconfig' file that should be taken
* into account with next calls to [lint] and/or [format]. See [editorConfigFilePaths] to get the list of
* '.editorconfig' files which need to be observed.
*/
public fun reloadEditorConfigFile(path: Path) {
THREAD_SAFE_EDITOR_CONFIG_CACHE.reloadIfExists(
Resource.Resources.ofPath(path, StandardCharsets.UTF_8),
)
}
public companion object {
internal const val UTF8_BOM = "\uFEFF"
public const val STDIN_FILE: String = KtLint.STDIN_FILE
}
}
private fun RuleExecutionException.toKtLintRuleException(fileName: String?) =
KtLintRuleException(
line,
col,
ruleId,
"Rule '$ruleId' throws exception in file '$fileName' at position ($line:$col)",
cause,
)