Propagate reports from script dependency resolver
Compiler: show as compiler messages IDE: annotate code in a separate highlighting pass
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.cli.common.script
|
||||||
|
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
|
import org.jetbrains.kotlin.script.ScriptReportSink
|
||||||
|
import kotlin.script.dependencies.ScriptReport
|
||||||
|
|
||||||
|
class CliScriptReportSink(private val messageCollector: MessageCollector) : ScriptReportSink {
|
||||||
|
override fun attachReports(scriptFile: VirtualFile, reports: List<ScriptReport>) {
|
||||||
|
reports.forEach {
|
||||||
|
messageCollector.report(it.severity.convertSeverity(), it.message, location(scriptFile, it.position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun location(scriptFile: VirtualFile, position: ScriptReport.Position?): CompilerMessageLocation? {
|
||||||
|
if (position == null) return CompilerMessageLocation.create(scriptFile.path)
|
||||||
|
|
||||||
|
return CompilerMessageLocation.create(scriptFile.path, position.startLine, position.startColumn, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ScriptReport.Severity.convertSeverity(): CompilerMessageSeverity = when(this) {
|
||||||
|
ScriptReport.Severity.ERROR -> CompilerMessageSeverity.ERROR
|
||||||
|
ScriptReport.Severity.WARNING -> CompilerMessageSeverity.WARNING
|
||||||
|
ScriptReport.Severity.INFO -> CompilerMessageSeverity.INFO
|
||||||
|
ScriptReport.Severity.DEBUG -> CompilerMessageSeverity.LOGGING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -73,6 +73,9 @@ import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PRO
|
|||||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
|
||||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING
|
||||||
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
|
import org.jetbrains.kotlin.cli.common.script.CliScriptReportSink
|
||||||
|
import org.jetbrains.kotlin.cli.common.script.KotlinScriptExternalImportsProviderImpl
|
||||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||||
import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker
|
import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker
|
||||||
import org.jetbrains.kotlin.cli.jvm.config.*
|
import org.jetbrains.kotlin.cli.jvm.config.*
|
||||||
@@ -104,7 +107,7 @@ import org.jetbrains.kotlin.resolve.lazy.declarations.CliDeclarationProviderFact
|
|||||||
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService
|
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider
|
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptExternalImportsProvider
|
import org.jetbrains.kotlin.script.KotlinScriptExternalImportsProvider
|
||||||
import org.jetbrains.kotlin.cli.common.script.KotlinScriptExternalImportsProviderImpl
|
import org.jetbrains.kotlin.script.ScriptReportSink
|
||||||
import org.jetbrains.kotlin.utils.PathUtil
|
import org.jetbrains.kotlin.utils.PathUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -173,7 +176,8 @@ class KotlinCoreEnvironment private constructor(
|
|||||||
project.registerService(ModuleVisibilityManager::class.java, CliModuleVisibilityManagerImpl(configFiles == EnvironmentConfigFiles.JVM_CONFIG_FILES))
|
project.registerService(ModuleVisibilityManager::class.java, CliModuleVisibilityManagerImpl(configFiles == EnvironmentConfigFiles.JVM_CONFIG_FILES))
|
||||||
|
|
||||||
registerProjectServicesForCLI(projectEnvironment)
|
registerProjectServicesForCLI(projectEnvironment)
|
||||||
registerProjectServices(projectEnvironment)
|
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||||
|
registerProjectServices(projectEnvironment, messageCollector)
|
||||||
|
|
||||||
sourceFiles += CompileEnvironmentUtil.getKtFiles(project, getSourceRootsCheckingForDuplicates(), this.configuration, {
|
sourceFiles += CompileEnvironmentUtil.getKtFiles(project, getSourceRootsCheckingForDuplicates(), this.configuration, {
|
||||||
message ->
|
message ->
|
||||||
@@ -193,8 +197,6 @@ class KotlinCoreEnvironment private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
|
||||||
|
|
||||||
classpathRootsResolver = ClasspathRootsResolver(
|
classpathRootsResolver = ClasspathRootsResolver(
|
||||||
PsiManager.getInstance(project), messageCollector,
|
PsiManager.getInstance(project), messageCollector,
|
||||||
configuration.getList(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES),
|
configuration.getList(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES),
|
||||||
@@ -517,13 +519,16 @@ class KotlinCoreEnvironment private constructor(
|
|||||||
|
|
||||||
// made public for Upsource
|
// made public for Upsource
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun registerProjectServices(projectEnvironment: JavaCoreProjectEnvironment) {
|
fun registerProjectServices(projectEnvironment: JavaCoreProjectEnvironment, messageCollector: MessageCollector?) {
|
||||||
with (projectEnvironment.project) {
|
with (projectEnvironment.project) {
|
||||||
val kotlinScriptDefinitionProvider = KotlinScriptDefinitionProvider()
|
val kotlinScriptDefinitionProvider = KotlinScriptDefinitionProvider()
|
||||||
registerService(KotlinScriptDefinitionProvider::class.java, kotlinScriptDefinitionProvider)
|
registerService(KotlinScriptDefinitionProvider::class.java, kotlinScriptDefinitionProvider)
|
||||||
registerService(KotlinScriptExternalImportsProvider::class.java, KotlinScriptExternalImportsProviderImpl(projectEnvironment.project, kotlinScriptDefinitionProvider))
|
registerService(KotlinScriptExternalImportsProvider::class.java, KotlinScriptExternalImportsProviderImpl(projectEnvironment.project, kotlinScriptDefinitionProvider))
|
||||||
registerService(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this))
|
registerService(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this))
|
||||||
registerService(KtLightClassForFacade.FacadeStubCache::class.java, KtLightClassForFacade.FacadeStubCache(this))
|
registerService(KtLightClassForFacade.FacadeStubCache::class.java, KtLightClassForFacade.FacadeStubCache(this))
|
||||||
|
if (messageCollector != null) {
|
||||||
|
registerService(ScriptReportSink::class.java, CliScriptReportSink(messageCollector))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.script
|
package org.jetbrains.kotlin.script
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.ServiceManager
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
@@ -27,7 +28,7 @@ import kotlin.reflect.KClass
|
|||||||
import kotlin.script.dependencies.ScriptContents
|
import kotlin.script.dependencies.ScriptContents
|
||||||
import kotlin.script.dependencies.ScriptDependencies
|
import kotlin.script.dependencies.ScriptDependencies
|
||||||
|
|
||||||
abstract class KotlinScriptExternalImportsProviderBase(private val project: Project): KotlinScriptExternalImportsProvider {
|
abstract class KotlinScriptExternalImportsProviderBase(private val project: Project) : KotlinScriptExternalImportsProvider {
|
||||||
fun getScriptContents(scriptDefinition: KotlinScriptDefinition, file: VirtualFile)
|
fun getScriptContents(scriptDefinition: KotlinScriptDefinition, file: VirtualFile)
|
||||||
= BasicScriptContents(file, getAnnotations = { loadAnnotations(scriptDefinition, file) })
|
= BasicScriptContents(file, getAnnotations = { loadAnnotations(scriptDefinition, file) })
|
||||||
|
|
||||||
@@ -61,10 +62,12 @@ abstract class KotlinScriptExternalImportsProviderBase(private val project: Proj
|
|||||||
file: VirtualFile
|
file: VirtualFile
|
||||||
): ScriptDependencies? {
|
): ScriptDependencies? {
|
||||||
val scriptContents = getScriptContents(scriptDef, file)
|
val scriptContents = getScriptContents(scriptDef, file)
|
||||||
return scriptDef.dependencyResolver.resolve(
|
val result = scriptDef.dependencyResolver.resolve(
|
||||||
scriptContents,
|
scriptContents,
|
||||||
(scriptDef as? KotlinScriptDefinitionFromAnnotatedTemplate)?.environment ?: emptyMap()
|
(scriptDef as? KotlinScriptDefinitionFromAnnotatedTemplate)?.environment ?: emptyMap()
|
||||||
).dependencies
|
)
|
||||||
|
ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports)
|
||||||
|
return result.dependencies
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.script
|
||||||
|
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import kotlin.script.dependencies.ScriptReport
|
||||||
|
|
||||||
|
interface ScriptReportSink {
|
||||||
|
fun attachReports(scriptFile: VirtualFile, reports: List<ScriptReport>)
|
||||||
|
}
|
||||||
@@ -200,9 +200,10 @@ class TestMessageCollector : MessageCollector {
|
|||||||
override fun hasErrors(): Boolean = messages.any { it.severity == CompilerMessageSeverity.EXCEPTION || it.severity == CompilerMessageSeverity.ERROR }
|
override fun hasErrors(): Boolean = messages.any { it.severity == CompilerMessageSeverity.EXCEPTION || it.severity == CompilerMessageSeverity.ERROR }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TestMessageCollector.assertHasMessage(msg: String) {
|
fun TestMessageCollector.assertHasMessage(msg: String, desiredSeverity: CompilerMessageSeverity? = null) {
|
||||||
assert(messages.any { it.message.contains(msg) }) {
|
assert(messages.any { it.message.contains(msg) && (desiredSeverity == null || it.severity == desiredSeverity) }) {
|
||||||
"Expecting message \"$msg\", actual:\n${messages.joinToString("\n") { it.message }}"
|
"Expecting message \"$msg\" with severity ${desiredSeverity?.toString() ?: "Any"}, actual:\n" +
|
||||||
|
messages.joinToString("\n") { it.severity.toString() + ": " + it.message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
|
|||||||
import org.jetbrains.kotlin.codegen.CompilationException
|
import org.jetbrains.kotlin.codegen.CompilationException
|
||||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||||
import org.jetbrains.kotlin.config.addKotlinSourceRoot
|
import org.jetbrains.kotlin.config.addKotlinSourceRoot
|
||||||
|
import org.jetbrains.kotlin.daemon.TestMessageCollector
|
||||||
|
import org.jetbrains.kotlin.daemon.assertHasMessage
|
||||||
import org.jetbrains.kotlin.daemon.toFile
|
import org.jetbrains.kotlin.daemon.toFile
|
||||||
import org.jetbrains.kotlin.script.InvalidScriptResolverAnnotation
|
import org.jetbrains.kotlin.script.InvalidScriptResolverAnnotation
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||||
@@ -236,6 +238,17 @@ class ScriptTemplateTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testScriptErrorReporting() {
|
||||||
|
val messageCollector = TestMessageCollector()
|
||||||
|
compileScript("fib.kts", ScriptReportingErrors::class, messageCollector = messageCollector)
|
||||||
|
|
||||||
|
messageCollector.assertHasMessage("error", desiredSeverity = CompilerMessageSeverity.ERROR)
|
||||||
|
messageCollector.assertHasMessage("warning", desiredSeverity = CompilerMessageSeverity.WARNING)
|
||||||
|
messageCollector.assertHasMessage("info", desiredSeverity = CompilerMessageSeverity.INFO)
|
||||||
|
messageCollector.assertHasMessage("debug", desiredSeverity = CompilerMessageSeverity.LOGGING)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSmokeScriptException() {
|
fun testSmokeScriptException() {
|
||||||
val aClass = compileScript("smoke_exception.kts", ScriptWithArrayParam::class)
|
val aClass = compileScript("smoke_exception.kts", ScriptWithArrayParam::class)
|
||||||
@@ -270,24 +283,20 @@ class ScriptTemplateTest {
|
|||||||
scriptTemplate: KClass<out Any>,
|
scriptTemplate: KClass<out Any>,
|
||||||
environment: Map<String, Any?>? = null,
|
environment: Map<String, Any?>? = null,
|
||||||
runIsolated: Boolean = true,
|
runIsolated: Boolean = true,
|
||||||
suppressOutput: Boolean = false,
|
messageCollector: MessageCollector = PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false),
|
||||||
includeKotlinRuntime: Boolean = true
|
includeKotlinRuntime: Boolean = true
|
||||||
): Class<*>? =
|
): Class<*>? =
|
||||||
compileScriptImpl("compiler/testData/script/" + scriptPath, KotlinScriptDefinitionFromAnnotatedTemplate(
|
compileScriptImpl("compiler/testData/script/" + scriptPath, KotlinScriptDefinitionFromAnnotatedTemplate(
|
||||||
scriptTemplate, null, null, environment
|
scriptTemplate, null, null, environment
|
||||||
), runIsolated, suppressOutput, includeKotlinRuntime)
|
), runIsolated, messageCollector, includeKotlinRuntime)
|
||||||
|
|
||||||
private fun compileScriptImpl(
|
private fun compileScriptImpl(
|
||||||
scriptPath: String,
|
scriptPath: String,
|
||||||
scriptDefinition: KotlinScriptDefinition,
|
scriptDefinition: KotlinScriptDefinition,
|
||||||
runIsolated: Boolean,
|
runIsolated: Boolean,
|
||||||
suppressOutput: Boolean,
|
messageCollector: MessageCollector,
|
||||||
includeKotlinRuntime: Boolean
|
includeKotlinRuntime: Boolean
|
||||||
): Class<*>? {
|
): Class<*>? {
|
||||||
val messageCollector =
|
|
||||||
if (suppressOutput) MessageCollector.NONE
|
|
||||||
else PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false)
|
|
||||||
|
|
||||||
val rootDisposable = Disposer.newDisposable()
|
val rootDisposable = Disposer.newDisposable()
|
||||||
try {
|
try {
|
||||||
val configuration = KotlinTestUtils.newConfiguration(if (includeKotlinRuntime) ConfigurationKind.ALL else ConfigurationKind.JDK_ONLY, TestJdkKind.FULL_JDK)
|
val configuration = KotlinTestUtils.newConfiguration(if (includeKotlinRuntime) ConfigurationKind.ALL else ConfigurationKind.JDK_ONLY, TestJdkKind.FULL_JDK)
|
||||||
@@ -337,7 +346,7 @@ open class TestKotlinScriptDummyDependenciesResolver : DependenciesResolver {
|
|||||||
?: emptyList()
|
?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestKotlinScriptDependenciesResolver : TestKotlinScriptDummyDependenciesResolver() {
|
open class TestKotlinScriptDependenciesResolver : TestKotlinScriptDummyDependenciesResolver() {
|
||||||
|
|
||||||
private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() }
|
private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() }
|
||||||
|
|
||||||
@@ -369,6 +378,24 @@ class TestKotlinScriptDependenciesResolver : TestKotlinScriptDummyDependenciesRe
|
|||||||
|
|
||||||
class TestParamClass(@Suppress("unused") val memberNum: Int)
|
class TestParamClass(@Suppress("unused") val memberNum: Int)
|
||||||
|
|
||||||
|
class ErrorReportingResolver : TestKotlinScriptDependenciesResolver() {
|
||||||
|
override fun resolve(
|
||||||
|
scriptContents: ScriptContents,
|
||||||
|
environment: Environment
|
||||||
|
): ResolveResult {
|
||||||
|
return ResolveResult.Success(
|
||||||
|
super.resolve(scriptContents, environment).dependencies!!,
|
||||||
|
listOf(
|
||||||
|
ScriptReport("error", ScriptReport.Severity.ERROR, null),
|
||||||
|
ScriptReport("warning", ScriptReport.Severity.WARNING, ScriptReport.Position(1, 0)),
|
||||||
|
ScriptReport("info", ScriptReport.Severity.INFO, ScriptReport.Position(2, 0)),
|
||||||
|
ScriptReport("debug", ScriptReport.Severity.DEBUG, ScriptReport.Position(3, 0))
|
||||||
|
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ScriptTemplateDefinition(
|
@ScriptTemplateDefinition(
|
||||||
scriptFilePattern =".*\\.kts",
|
scriptFilePattern =".*\\.kts",
|
||||||
resolver = TestKotlinScriptDummyDependenciesResolver::class)
|
resolver = TestKotlinScriptDummyDependenciesResolver::class)
|
||||||
@@ -420,6 +447,9 @@ abstract class ScriptWithNullableProjection(val param: Array<String?>)
|
|||||||
@ScriptTemplateDefinition(resolver = TestKotlinScriptDependenciesResolver::class)
|
@ScriptTemplateDefinition(resolver = TestKotlinScriptDependenciesResolver::class)
|
||||||
abstract class ScriptWithArray2DParam(val param: Array<Array<in String>>)
|
abstract class ScriptWithArray2DParam(val param: Array<Array<in String>>)
|
||||||
|
|
||||||
|
@ScriptTemplateDefinition(resolver = ErrorReportingResolver::class)
|
||||||
|
abstract class ScriptReportingErrors(val num: Int)
|
||||||
|
|
||||||
@Target(AnnotationTarget.FILE)
|
@Target(AnnotationTarget.FILE)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class DependsOn(val path: String)
|
annotation class DependsOn(val path: String)
|
||||||
|
|||||||
@@ -38,6 +38,5 @@
|
|||||||
<orderEntry type="library" name="uast-java" level="project" />
|
<orderEntry type="library" name="uast-java" level="project" />
|
||||||
<orderEntry type="module" module-name="android-extensions-compiler" />
|
<orderEntry type="module" module-name="android-extensions-compiler" />
|
||||||
<orderEntry type="module" module-name="frontend.script" />
|
<orderEntry type="module" module-name="frontend.script" />
|
||||||
<orderEntry type="library" name="kotlinx-coroutines-jdk8" level="project" />
|
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.highlighter
|
||||||
|
|
||||||
|
import com.intellij.codeHighlighting.Pass
|
||||||
|
import com.intellij.codeHighlighting.TextEditorHighlightingPass
|
||||||
|
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory
|
||||||
|
import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar
|
||||||
|
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||||
|
import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil
|
||||||
|
import com.intellij.lang.annotation.Annotation
|
||||||
|
import com.intellij.lang.annotation.HighlightSeverity
|
||||||
|
import com.intellij.lang.annotation.HighlightSeverity.*
|
||||||
|
import com.intellij.openapi.components.AbstractProjectComponent
|
||||||
|
import com.intellij.openapi.editor.Document
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.progress.ProgressIndicator
|
||||||
|
import com.intellij.openapi.project.DumbAware
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.IdeScriptReportSink
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import kotlin.script.dependencies.ScriptReport
|
||||||
|
|
||||||
|
class ScriptExternalHighlightingPass(
|
||||||
|
private val file: KtFile,
|
||||||
|
document: Document
|
||||||
|
) : TextEditorHighlightingPass(file.project, document), DumbAware {
|
||||||
|
override fun doCollectInformation(progress: ProgressIndicator) = Unit
|
||||||
|
|
||||||
|
override fun doApplyInformationToEditor() {
|
||||||
|
val document = document ?: return
|
||||||
|
|
||||||
|
val reports = file.virtualFile.getUserData(IdeScriptReportSink.Reports) ?: return
|
||||||
|
|
||||||
|
val annotations = reports.mapNotNull { (message, severity, position) ->
|
||||||
|
val (startOffset, endOffset) = computeOffsets(document, position) ?: return@mapNotNull null
|
||||||
|
Annotation(
|
||||||
|
startOffset,
|
||||||
|
endOffset,
|
||||||
|
severity.convertSeverity() ?: return@mapNotNull null,
|
||||||
|
message,
|
||||||
|
message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val infos = annotations.map { HighlightInfo.fromAnnotation(it) }
|
||||||
|
UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument!!, 0, file.textLength, infos, colorsScheme, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeOffsets(document: Document, position: ScriptReport.Position?): Pair<Int, Int>? {
|
||||||
|
if (position == null) {
|
||||||
|
// TODO: better presentation of those errors
|
||||||
|
// if no position was specified, mark first two lines as an error
|
||||||
|
return computeOffsets(document, ScriptReport.Position(0, 0, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
val startLine = position.startLine.coerceLineIn(document)
|
||||||
|
val startOffset = document.offsetBy(startLine, position.startColumn)
|
||||||
|
|
||||||
|
val endLine = position.endLine?.coerceAtLeast(startLine)?.coerceLineIn(document) ?: startLine
|
||||||
|
val endOffset = document.offsetBy(
|
||||||
|
endLine,
|
||||||
|
position.endColumn ?: document.getLineEndOffset(endLine)
|
||||||
|
).coerceAtLeast(startOffset)
|
||||||
|
|
||||||
|
// TODO: presentation when range is empty?
|
||||||
|
return startOffset to endOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Int.coerceLineIn(document: Document) = coerceIn(0, document.lineCount - 1)
|
||||||
|
|
||||||
|
private fun Document.offsetBy(line: Int, col: Int): Int {
|
||||||
|
val offset = (getLineStartOffset(line) + col).
|
||||||
|
coerceIn(getLineStartOffset(line), getLineEndOffset(line))
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ScriptReport.Severity.convertSeverity(): HighlightSeverity? {
|
||||||
|
return when (this) {
|
||||||
|
ScriptReport.Severity.ERROR -> ERROR
|
||||||
|
ScriptReport.Severity.WARNING -> WARNING
|
||||||
|
ScriptReport.Severity.INFO -> INFORMATION
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Factory(project: Project, registrar: TextEditorHighlightingPassRegistrar)
|
||||||
|
: AbstractProjectComponent(project), TextEditorHighlightingPassFactory {
|
||||||
|
init {
|
||||||
|
registrar.registerTextEditorHighlightingPass(this, TextEditorHighlightingPassRegistrar.Anchor.BEFORE, Pass.UPDATE_FOLDING, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
|
||||||
|
if (file !is KtFile) return null
|
||||||
|
return ScriptExternalHighlightingPass(file, editor.document)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.core.script
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jetbrains.kotlin.script.ScriptReportSink
|
||||||
|
import kotlin.script.dependencies.ScriptReport
|
||||||
|
|
||||||
|
class IdeScriptReportSink : ScriptReportSink {
|
||||||
|
override fun attachReports(scriptFile: VirtualFile, reports: List<ScriptReport>) {
|
||||||
|
// TODO: persist errors between launches?
|
||||||
|
scriptFile.putUserData(Reports, reports)
|
||||||
|
}
|
||||||
|
|
||||||
|
object Reports : Key<List<ScriptReport>>("KOTLIN_SCRIPT_REPORTS")
|
||||||
|
}
|
||||||
+1
@@ -238,6 +238,7 @@ class KotlinScriptConfigurationManager(
|
|||||||
cacheLock.read {
|
cacheLock.read {
|
||||||
val lastTimeStamp = cache[path]?.requestInProgress?.timeStamp
|
val lastTimeStamp = cache[path]?.requestInProgress?.timeStamp
|
||||||
if (lastTimeStamp == currentTimeStamp) {
|
if (lastTimeStamp == currentTimeStamp) {
|
||||||
|
ServiceManager.getService(project, ScriptReportSink::class.java)?.attachReports(file, result.reports)
|
||||||
if (cacheSync(result.dependencies ?: ScriptDependencies.Empty, oldDataAndRequest?.dependencies, path, file)) {
|
if (cacheSync(result.dependencies ?: ScriptDependencies.Empty, oldDataAndRequest?.dependencies, path, file)) {
|
||||||
invalidateLocalCaches()
|
invalidateLocalCaches()
|
||||||
notifyRootsChanged()
|
notifyRootsChanged()
|
||||||
|
|||||||
@@ -38,6 +38,9 @@
|
|||||||
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
|
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
|
||||||
<skipForDefaultProject/>
|
<skipForDefaultProject/>
|
||||||
</component>
|
</component>
|
||||||
|
<component>
|
||||||
|
<implementation-class>org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory</implementation-class>
|
||||||
|
</component>
|
||||||
<component>
|
<component>
|
||||||
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
|
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
|
||||||
</component>
|
</component>
|
||||||
@@ -272,6 +275,9 @@
|
|||||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager"
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager"
|
||||||
serviceImplementation="org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager"/>
|
serviceImplementation="org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager"/>
|
||||||
|
|
||||||
|
<projectService serviceInterface="org.jetbrains.kotlin.script.ScriptReportSink"
|
||||||
|
serviceImplementation="org.jetbrains.kotlin.idea.core.script.IdeScriptReportSink"/>
|
||||||
|
|
||||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder"
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder"
|
||||||
serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder"/>
|
serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder"/>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
val <error descr="Can't use">java</error> = "j"
|
||||||
|
val <warning descr="Shouldn't use">scala</warning> = "s"
|
||||||
|
|
||||||
|
val result = <error descr="Can't use">java</error> + <warning descr="Shouldn't use">scala</warning>
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package custom.scriptDefinition
|
||||||
|
|
||||||
|
import kotlin.script.dependencies.*
|
||||||
|
import kotlin.script.templates.*
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class TestDependenciesResolver : DependenciesResolver {
|
||||||
|
override fun resolve(scriptContents: ScriptContents, environment: Environment): DependenciesResolver.ResolveResult {
|
||||||
|
val reports = ArrayList<ScriptReport>()
|
||||||
|
scriptContents.text?.let { text ->
|
||||||
|
text.lines().forEachIndexed { lineIndex, line ->
|
||||||
|
val adjustedLine = line.replace(Regex("(<error descr=\"Can't use\">)|(</error>)|(<warning descr=\"Shouldn't use\">)|(</warning>)"), "")
|
||||||
|
Regex("java").findAll(adjustedLine).forEach {
|
||||||
|
reports.add(
|
||||||
|
ScriptReport(
|
||||||
|
"Can't use",
|
||||||
|
ScriptReport.Severity.ERROR,
|
||||||
|
ScriptReport.Position(lineIndex, it.range.first, lineIndex, it.range.last + 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Regex("scala").findAll(adjustedLine).forEach {
|
||||||
|
reports.add(
|
||||||
|
ScriptReport(
|
||||||
|
"Shouldn't use",
|
||||||
|
ScriptReport.Severity.WARNING,
|
||||||
|
ScriptReport.Position(lineIndex, it.range.first, lineIndex, it.range.last + 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DependenciesResolver.ResolveResult.Success(
|
||||||
|
ScriptDependencies(
|
||||||
|
classpath = listOf(environment["template-classes"] as File)
|
||||||
|
),
|
||||||
|
reports
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ScriptTemplateDefinition(TestDependenciesResolver::class, scriptFilePattern = "script.kts")
|
||||||
|
class Template : Base()
|
||||||
|
|
||||||
|
open class Base {
|
||||||
|
val i = 3
|
||||||
|
val str = ""
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
val <error descr="Can't use">java = "j"</error>
|
||||||
|
val <warning descr="Shouldn't use">scala = "s"</warning>
|
||||||
|
|
||||||
|
val result = <error descr="Can't use">java +</error>
|
||||||
|
<warning descr="Shouldn't use">scala</warning>
|
||||||
Vendored
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package custom.scriptDefinition
|
||||||
|
|
||||||
|
import kotlin.script.dependencies.*
|
||||||
|
import kotlin.script.templates.*
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.Future
|
||||||
|
|
||||||
|
class TestDependenciesResolver : ScriptDependenciesResolver {
|
||||||
|
override fun resolve(
|
||||||
|
script: ScriptContents,
|
||||||
|
environment: Map<String, Any?>?,
|
||||||
|
report: (ScriptDependenciesResolver.ReportSeverity, String, ScriptContents.Position?) -> Unit, previousDependencies: KotlinScriptExternalDependencies?
|
||||||
|
): Future<KotlinScriptExternalDependencies?> {
|
||||||
|
script.text?.let { text ->
|
||||||
|
text.lines().forEachIndexed { lineIndex, line ->
|
||||||
|
val adjustedLine = line.replace(Regex("(<error descr=\"Can't use\">)|(</error>)|(<warning descr=\"Shouldn't use\">)|(</warning>)"), "")
|
||||||
|
Regex("java").findAll(adjustedLine).forEach {
|
||||||
|
val columnIndex = it.range.first
|
||||||
|
report(ScriptDependenciesResolver.ReportSeverity.ERROR, "Can't use", ScriptContents.Position(lineIndex, columnIndex))
|
||||||
|
}
|
||||||
|
Regex("scala").findAll(adjustedLine).forEach {
|
||||||
|
val columnIndex = it.range.first
|
||||||
|
report(ScriptDependenciesResolver.ReportSeverity.WARNING, "Shouldn't use", ScriptContents.Position(lineIndex, columnIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return CompletableFuture.completedFuture(
|
||||||
|
object : KotlinScriptExternalDependencies {
|
||||||
|
override val classpath: Iterable<File> = listOf(environment?.get("template-classes") as File)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ScriptTemplateDefinition(TestDependenciesResolver::class, scriptFilePattern = "script.kts")
|
||||||
|
class Template : Base()
|
||||||
|
|
||||||
|
open class Base {
|
||||||
|
val i = 3
|
||||||
|
val str = ""
|
||||||
|
}
|
||||||
+12
@@ -54,6 +54,18 @@ public class ScriptConfigurationHighlightingTestGenerated extends AbstractScript
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("doNotSpeakAboutJava")
|
||||||
|
public void testDoNotSpeakAboutJava() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/doNotSpeakAboutJava/");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("doNotSpeakAboutJavaLegacy")
|
||||||
|
public void testDoNotSpeakAboutJavaLegacy() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/doNotSpeakAboutJavaLegacy/");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("simple")
|
@TestMetadata("simple")
|
||||||
public void testSimple() throws Exception {
|
public void testSimple() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/simple/");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/script/definition/highlighting/simple/");
|
||||||
|
|||||||
Reference in New Issue
Block a user