diff --git a/compiler/ir/ir.interpreter/build.gradle.kts b/compiler/ir/ir.interpreter/build.gradle.kts index f4bbabce948..cad74271951 100644 --- a/compiler/ir/ir.interpreter/build.gradle.kts +++ b/compiler/ir/ir.interpreter/build.gradle.kts @@ -6,6 +6,8 @@ plugins { dependencies { compileOnly(project(":compiler:ir.tree")) compileOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false } + + implementation(project(":core:compiler.common.js")) } optInToUnsafeDuringIrConstructionAPI() diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstExpressionTransformer.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstExpressionTransformer.kt index 7a9b336733a..5e06fd4cb1b 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstExpressionTransformer.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstExpressionTransformer.kt @@ -42,14 +42,14 @@ internal abstract class IrConstExpressionTransformer( override fun visitClass(declaration: IrClass, data: Data): IrStatement { if (declaration.kind == ClassKind.ANNOTATION_CLASS) { - return super.visitClass(declaration, data.copy(inAnnotation = true)) + return super.visitClass(declaration, data.copy(inConstantExpression = true)) } return super.visitClass(declaration, data) } override fun visitCall(expression: IrCall, data: Data): IrElement { if (expression.canBeInterpreted()) { - return expression.interpret(failAsError = data.inAnnotation) + return expression.interpret(failAsError = data.inConstantExpression) } return super.visitCall(expression, data) } @@ -68,7 +68,7 @@ internal abstract class IrConstExpressionTransformer( override fun visitGetField(expression: IrGetField, data: Data): IrExpression { if (expression.canBeInterpreted()) { - return expression.interpret(failAsError = data.inAnnotation) + return expression.interpret(failAsError = data.inConstantExpression) } return super.visitGetField(expression, data) } @@ -78,7 +78,7 @@ internal abstract class IrConstExpressionTransformer( this.startOffset, this.endOffset, expression.type, listOf(this@wrapInStringConcat) ) - fun IrExpression.wrapInToStringConcatAndInterpret(): IrExpression = wrapInStringConcat().interpret(failAsError = data.inAnnotation) + fun IrExpression.wrapInToStringConcatAndInterpret(): IrExpression = wrapInStringConcat().interpret(failAsError = data.inConstantExpression) fun IrExpression.getConstStringOrEmpty(): String = if (this is IrConst<*>) value.toString() else "" // If we have some complex expression in arguments (like some `IrComposite`) we will skip it, diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstOnlyNecessaryTransformer.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstOnlyNecessaryTransformer.kt index 1c299c79578..30233ccbe7e 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstOnlyNecessaryTransformer.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstOnlyNecessaryTransformer.kt @@ -11,16 +11,19 @@ import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.declarations.IrField import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.interpreter.IrInterpreter import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterChecker import org.jetbrains.kotlin.ir.interpreter.isConst import org.jetbrains.kotlin.ir.interpreter.property +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable +import org.jetbrains.kotlin.name.JsStandardClassIds /** - * This transformer will visit all expressions and will evaluate only those that are necessary. By "necessary" we mean expressions - * that are used in `const val` and inside annotations. + * This transformer will visit all expressions and will evaluate only those that are necessary. + * By "necessary" we mean expressions that are used in `const val`, inside annotations and js() call arguments. */ internal class IrConstOnlyNecessaryTransformer( interpreter: IrInterpreter, @@ -35,10 +38,13 @@ internal class IrConstOnlyNecessaryTransformer( ) : IrConstExpressionTransformer( interpreter, irFile, mode, checker, evaluatedConstTracker, inlineConstTracker, onWarning, onError, suppressExceptions ) { + private val jsCodeFqName = JsStandardClassIds.Callables.JsCode.asSingleFqName() + override fun visitCall(expression: IrCall, data: Data): IrElement { val isConstGetter = expression.symbol.owner.property.isConst - if (!data.inAnnotation && !isConstGetter) { - expression.transformChildren(this, data) + val isJsCodeCall = expression.symbol.owner.fqNameWhenAvailable == jsCodeFqName + if (isJsCodeCall || (!data.inConstantExpression && !isConstGetter)) { + expression.transformChildren(this, data.copy(inConstantExpression = data.inConstantExpression || isJsCodeCall)) return expression } return super.visitCall(expression, data) @@ -46,12 +52,12 @@ internal class IrConstOnlyNecessaryTransformer( override fun visitGetField(expression: IrGetField, data: Data): IrExpression { val isConst = expression.symbol.owner.property.isConst - if (!data.inAnnotation && !isConst) return expression + if (!data.inConstantExpression && !isConst) return expression return super.visitGetField(expression, data) } override fun visitStringConcatenation(expression: IrStringConcatenation, data: Data): IrExpression { - if (!data.inAnnotation) { + if (!data.inConstantExpression) { expression.transformChildren(this, data) return expression } diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt index c5333f885d4..dbb72293359 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt @@ -99,7 +99,7 @@ internal abstract class IrConstTransformer( private val onError: (IrFile, IrElement, IrErrorExpression) -> Unit, private val suppressExceptions: Boolean, ) : IrElementTransformer { - internal data class Data(val inAnnotation: Boolean = false) + internal data class Data(val inConstantExpression: Boolean = false) private fun IrExpression.warningIfError(original: IrExpression): IrExpression { if (this is IrErrorExpression) { diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibCheckers.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibCheckers.kt index 4419383021a..6d61cc99175 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibCheckers.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibCheckers.kt @@ -9,10 +9,15 @@ import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.backend.js.checkers.declarations.JsKlibEsModuleExportsChecker import org.jetbrains.kotlin.ir.backend.js.checkers.declarations.JsKlibOtherModuleExportsChecker -import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName -import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.backend.js.checkers.expressions.JsKlibJsCodeCallChecker +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.library.SerializedIrFile object JsKlibCheckers { @@ -21,17 +26,51 @@ object JsKlibCheckers { JsKlibOtherModuleExportsChecker ) + private val callCheckers = listOf( + JsKlibJsCodeCallChecker + ) + fun check( cleanFiles: List, - dirtyFiles: List, + dirtyModule: IrModuleFragment, exportedNames: Map>, diagnosticReporter: DiagnosticReporter, configuration: CompilerConfiguration ) { - val reporter = KtDiagnosticReporterWithImplicitIrBasedContext(diagnosticReporter, configuration.languageVersionSettings) - val exportedDeclarations = JsKlibExportingDeclaration.collectDeclarations(cleanFiles, dirtyFiles, exportedNames) - for (checker in exportedDeclarationsCheckers) { - checker.check(exportedDeclarations, reporter) - } + dirtyModule.acceptVoid(object : IrElementVisitorVoid { + private val reporter = KtDiagnosticReporterWithImplicitIrBasedContext(diagnosticReporter, configuration.languageVersionSettings) + private val diagnosticContext = JsKlibDiagnosticContext(configuration) + + override fun visitElement(element: IrElement) { + if (element is IrDeclaration) { + diagnosticContext.withDeclarationScope(element) { + element.acceptChildrenVoid(this) + } + } else { + element.acceptChildrenVoid(this) + } + } + + override fun visitModuleFragment(declaration: IrModuleFragment) { + val exportedDeclarations = JsKlibExportingDeclaration.collectDeclarations(cleanFiles, declaration.files, exportedNames) + for (checker in exportedDeclarationsCheckers) { + checker.check(exportedDeclarations, this.diagnosticContext, reporter) + } + super.visitModuleFragment(declaration) + } + + override fun visitFile(declaration: IrFile) { + diagnosticContext.withFileScope(declaration) { + super.visitFile(declaration) + } + } + + override fun visitCall(expression: IrCall) { + for (checker in callCheckers) { + checker.check(expression, this.diagnosticContext, reporter) + } + super.visitCall(expression) + } + }) } } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDeclarationsChecker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDeclarationsChecker.kt index 6eb6dff902d..1ae9b6b686f 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDeclarationsChecker.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDeclarationsChecker.kt @@ -8,7 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.checkers import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext interface JsKlibDeclarationsChecker { - fun check(declarations: List, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) + fun check(declarations: List, context: JsKlibDiagnosticContext, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) } typealias JsKlibExportedDeclarationsChecker = JsKlibDeclarationsChecker diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDiagnosticContext.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDiagnosticContext.kt new file mode 100644 index 00000000000..937667a0d1e --- /dev/null +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDiagnosticContext.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.checkers + +import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.diagnostics.KtDiagnosticReporterWithContext +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.declarations.IrFile + +class JsKlibDiagnosticContext(val compilerConfiguration: CompilerConfiguration) { + var containingDeclaration: IrDeclaration? = null + private set + + var containingFile: IrFile? = null + private set + + fun withDeclarationScope(declaration: IrDeclaration, f: () -> Unit) { + val prevDeclaration = containingDeclaration + try { + containingDeclaration = declaration + f() + } finally { + containingDeclaration = prevDeclaration + } + } + + fun withFileScope(file: IrFile, f: () -> Unit) { + val prevFile = containingFile + try { + containingFile = file + f() + } finally { + containingFile = prevFile + } + } +} + +fun KtDiagnosticReporterWithImplicitIrBasedContext.at( + declaration: IrDeclaration, + context: JsKlibDiagnosticContext, +): KtDiagnosticReporterWithContext.DiagnosticContextImpl { + return context.containingFile?.let { at(declaration, it) } ?: at(declaration) +} + +fun KtDiagnosticReporterWithImplicitIrBasedContext.at( + irElement: IrElement, + context: JsKlibDiagnosticContext, +): KtDiagnosticReporterWithContext.DiagnosticContextImpl { + val file = context.containingFile + if (file != null) { + return at(irElement, file) + } + + val declaration = context.containingDeclaration + if (declaration != null) { + return at(irElement, declaration) + } + + // Should never happen + error("Cannot find the expression containing declaration") +} diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibErrors.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibErrors.kt index db08243c547..ceb63ba05fd 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibErrors.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibErrors.kt @@ -7,14 +7,23 @@ package org.jetbrains.kotlin.ir.backend.js.checkers import com.intellij.psi.PsiElement import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap +import org.jetbrains.kotlin.diagnostics.error0 +import org.jetbrains.kotlin.diagnostics.error1 import org.jetbrains.kotlin.diagnostics.error2 import org.jetbrains.kotlin.diagnostics.rendering.* +import org.jetbrains.kotlin.diagnostics.warning0 +import org.jetbrains.kotlin.diagnostics.warning1 import org.jetbrains.kotlin.diagnostics.warning2 object JsKlibErrors { val EXPORTING_JS_NAME_CLASH by error2>() val EXPORTING_JS_NAME_CLASH_ES by warning2>() + val JSCODE_CAN_NOT_VERIFY_JAVASCRIPT by warning0() + val JSCODE_NO_JAVASCRIPT_PRODUCED by error0() + val JSCODE_ERROR by error1() + val JSCODE_WARNING by warning1() + init { RootDiagnosticRendererFactory.registerFactory(KtDefaultJsKlibErrorMessages) } @@ -43,5 +52,23 @@ private object KtDefaultJsKlibErrorMessages : BaseDiagnosticRendererFactory() { CommonRenderers.STRING, JS_KLIB_EXPORTS, ) + map.put( + JsKlibErrors.JSCODE_CAN_NOT_VERIFY_JAVASCRIPT, + "Cannot verify JavaScript code because the argument is not a constant string" + ) + map.put( + JsKlibErrors.JSCODE_NO_JAVASCRIPT_PRODUCED, + "An argument for the js() function must be non-empty JavaScript code" + ) + map.put( + JsKlibErrors.JSCODE_ERROR, + "JavaScript error: {0}", + CommonRenderers.STRING + ) + map.put( + JsKlibErrors.JSCODE_WARNING, + "JavaScript warning: {0}", + CommonRenderers.STRING + ) } } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibExpressionChecker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibExpressionChecker.kt new file mode 100644 index 00000000000..cc5f0374153 --- /dev/null +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibExpressionChecker.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.checkers + +import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression + +interface JsKlibExpressionChecker { + fun check(expression: E, context: JsKlibDiagnosticContext, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) +} + +typealias JsKlibCallChecker = JsKlibExpressionChecker diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibEsModuleExportsChecker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibEsModuleExportsChecker.kt index d039a1f7237..62fc10d2da1 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibEsModuleExportsChecker.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibEsModuleExportsChecker.kt @@ -6,14 +6,13 @@ package org.jetbrains.kotlin.ir.backend.js.checkers.declarations import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext -import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibExportingDeclaration -import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibExportedDeclarationsChecker -import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibErrors +import org.jetbrains.kotlin.ir.backend.js.checkers.* object JsKlibEsModuleExportsChecker : JsKlibExportedDeclarationsChecker { override fun check( declarations: List, - reporter: KtDiagnosticReporterWithImplicitIrBasedContext, + context: JsKlibDiagnosticContext, + reporter: KtDiagnosticReporterWithImplicitIrBasedContext ) { val allExportedNameClashes = declarations.groupBy { it.exportingName }.filterValues { it.size > 1 } @@ -21,7 +20,11 @@ object JsKlibEsModuleExportsChecker : JsKlibExportedDeclarationsChecker { for ((index, exportedDeclaration) in exportedDeclarationClashes.withIndex()) { val declaration = exportedDeclaration.declaration ?: continue val clashedWith = exportedDeclarationClashes.filterIndexed { i, _ -> i != index } - reporter.at(declaration).report(JsKlibErrors.EXPORTING_JS_NAME_CLASH_ES, exportedDeclaration.exportingName, clashedWith) + reporter.at(declaration, context).report( + JsKlibErrors.EXPORTING_JS_NAME_CLASH_ES, + exportedDeclaration.exportingName, + clashedWith + ) } } } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibOtherModuleExportsChecker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibOtherModuleExportsChecker.kt index c1a9d5de670..5a0da36f487 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibOtherModuleExportsChecker.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibOtherModuleExportsChecker.kt @@ -44,11 +44,19 @@ object JsKlibOtherModuleExportsChecker : JsKlibExportedDeclarationsChecker { } } - override fun check(declarations: List, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) { + override fun check( + declarations: List, + context: JsKlibDiagnosticContext, + reporter: KtDiagnosticReporterWithImplicitIrBasedContext + ) { val clashes = collectClashes(declarations) for ((declaration, clashedWith) in clashes) { if (declaration.declaration != null) { - reporter.at(declaration.declaration).report(JsKlibErrors.EXPORTING_JS_NAME_CLASH, declaration.exportingName, clashedWith) + reporter.at(declaration.declaration, context).report( + JsKlibErrors.EXPORTING_JS_NAME_CLASH, + declaration.exportingName, + clashedWith + ) } } } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/expressions/JsKlibJsCodeCallChecker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/expressions/JsKlibJsCodeCallChecker.kt new file mode 100644 index 00000000000..2598f1b25e1 --- /dev/null +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/expressions/JsKlibJsCodeCallChecker.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.checkers.expressions + +import com.google.gwt.dev.js.parserExceptions.AbortParsingException +import com.google.gwt.dev.js.rhino.CodePosition +import com.google.gwt.dev.js.rhino.ErrorReporter +import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext +import org.jetbrains.kotlin.config.languageVersionSettings +import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibCallChecker +import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibErrors +import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibDiagnosticContext +import org.jetbrains.kotlin.ir.backend.js.checkers.at +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.expressions.IrConstKind +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable +import org.jetbrains.kotlin.js.backend.ast.JsFunctionScope +import org.jetbrains.kotlin.js.backend.ast.JsProgram +import org.jetbrains.kotlin.js.backend.ast.JsRootScope +import org.jetbrains.kotlin.js.parser.parseExpressionOrStatement +import org.jetbrains.kotlin.name.JsStandardClassIds + +object JsKlibJsCodeCallChecker : JsKlibCallChecker { + private val jsCodeFqName = JsStandardClassIds.Callables.JsCode.asSingleFqName() + + override fun check(expression: IrCall, context: JsKlibDiagnosticContext, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) { + // Do not check IR from K1, because there are corresponding K1 FE checks in JsCallChecker + if (!context.compilerConfiguration.languageVersionSettings.languageVersion.usesK2) { + return + } + if (expression.symbol.owner.fqNameWhenAvailable != jsCodeFqName) { + return + } + + val jsCodeExpr = expression.getValueArgument(0) + // K2 frontend checker FirJsCodeConstantArgumentChecker checks that the passing argument is a constant. + // IrConstOnlyNecessaryTransformer must evaluate an argument expression and fold it into a string constant. + if (jsCodeExpr !is IrConst<*> || jsCodeExpr.kind != IrConstKind.String) { + // Do not ignore the error if the argument does not fit the expectations. + // The IR can be generated in the plugin, avoiding FE checks and IrConstOnlyNecessaryTransformer. + // Bugs are also possible in IrConstOnlyNecessaryTransformer. + reporter.at(jsCodeExpr ?: expression, context).report(JsKlibErrors.JSCODE_CAN_NOT_VERIFY_JAVASCRIPT) + return + } + + val jsCodeStr = IrConstKind.String.valueOf(jsCodeExpr) + + try { + val parserScope = JsFunctionScope(JsRootScope(JsProgram()), "") + val fileName = context.containingFile?.fileEntry?.name ?: "" + val jsErrorReporter = JsErrorReporter(jsCodeExpr, context, reporter) + val statements = parseExpressionOrStatement(jsCodeStr, jsErrorReporter, parserScope, CodePosition(0, 0), fileName) + if (statements.isNullOrEmpty()) { + reporter.at(jsCodeExpr, context).report(JsKlibErrors.JSCODE_NO_JAVASCRIPT_PRODUCED) + } + } catch (e: AbortParsingException) { + // ignore + } + } + + private class JsErrorReporter( + val codeExpression: IrExpression, + val context: JsKlibDiagnosticContext, + val reporter: KtDiagnosticReporterWithImplicitIrBasedContext, + ) : ErrorReporter { + override fun warning(message: String, startPosition: CodePosition, endPosition: CodePosition) { + reporter.at(codeExpression, context).report(JsKlibErrors.JSCODE_WARNING, message) + } + + override fun error(message: String, startPosition: CodePosition, endPosition: CodePosition) { + reporter.at(codeExpression, context).report(JsKlibErrors.JSCODE_ERROR, message) + throw AbortParsingException() + } + } +} diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt index dcec50eb8c9..e69ef210b3a 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt @@ -656,7 +656,7 @@ fun serializeModuleIntoKlib( if (builtInsPlatform == BuiltInsPlatform.JS) { val cleanFilesIrData = cleanFiles.map { it.irData } - JsKlibCheckers.check(cleanFilesIrData, moduleFragment.files, moduleExportedNames, diagnosticReporter, configuration) + JsKlibCheckers.check(cleanFilesIrData, moduleFragment, moduleExportedNames, diagnosticReporter, configuration) } val serializedIr =