From 1b3117e29cf75994831ed15fd3e56f32087eb167 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 24 Jul 2026 17:48:02 -0700 Subject: [PATCH 01/12] Simplify internal js and exports Signed-off-by: Tom --- .../kotlin/lang/temper/be/js/JsBackend.kt | 187 +--------------- .../kotlin/lang/temper/be/js/JsTranslator.kt | 208 ++++++++++++------ .../kotlin/lang/temper/be/js/JsBackendTest.kt | 28 ++- 3 files changed, 168 insertions(+), 255 deletions(-) diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt index 551fb213..af3fa7f7 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt @@ -5,7 +5,6 @@ import lang.temper.be.Backend import lang.temper.be.BackendHelpTopicKey import lang.temper.be.BackendHelpTopicKeys import lang.temper.be.BackendSetup -import lang.temper.be.globalPathSegment import lang.temper.be.tmpl.SupportNetwork import lang.temper.be.tmpl.TESTING_BASENAME import lang.temper.be.tmpl.TmpL @@ -17,11 +16,9 @@ import lang.temper.common.MimeType import lang.temper.common.json.JsonObject import lang.temper.common.json.JsonString import lang.temper.common.json.JsonValueBuilder -import lang.temper.common.putMultiSet import lang.temper.common.structure.PropertySink import lang.temper.common.structure.StructureParser import lang.temper.format.TokenSink -import lang.temper.frontend.Module import lang.temper.fs.declareResources import lang.temper.fs.loadResource import lang.temper.library.LibraryConfiguration @@ -41,7 +38,6 @@ import lang.temper.log.SameDirPseudoFilePathSegment import lang.temper.log.UNIX_FILE_SEGMENT_SEPARATOR import lang.temper.log.dirPath import lang.temper.log.filePath -import lang.temper.log.last import lang.temper.log.unknownPos import lang.temper.name.BackendId import lang.temper.name.BackendMeta @@ -209,7 +205,7 @@ class JsBackend private constructor( var stdTestingPath: FilePath? = null val testPaths = mutableSetOf() - var translations: List = + val translations: List = finished.modules.flatMap { tmpLModule -> val (supportCodes) = tmpLModule.findCommonTopLevels() val translator = JsTranslator( @@ -235,8 +231,6 @@ class JsBackend private constructor( } } } - // Link modules to shared definitions by export and import. - translations = linkModules(translations) val allOutputFiles = if (config.makeMetaDataFile) { val dependencyNames = mutableSetOf() @@ -408,140 +402,6 @@ class JsBackend private constructor( } } - private fun linkModules(translations: List): List { - val declaring = mutableMapOf>() - val exportedNames = mutableMapOf>() - // Find declarations. - for ((textFile, program) in translations) { - for (topLevel in program.topLevel) { - val declaredNames = findDeclaredNames(topLevel, exportedNames, textFile) - for (declaredName in declaredNames) { - declaring.putMultiSet(declaredName, textFile) - } - } - } - // Import into each according to their needs. - val importedBySomeOtherFile = mutableMapOf>() - for ((path, program) in translations) { - val needs = mutableMapOf>() - val localNameToExportedName = mutableMapOf() - walkDepthFirst(program) { - when (it) { - is Js.Identifier -> { - val name = it.name - val declarers = declaring[name] - if (declarers != null) { - if (path !in declarers) { - val declarer = declarers.first() - needs.putMultiSet(declarer, name) - importedBySomeOtherFile.putMultiSet(declarer, name) - } - } else { - it.sourceIdentifier?.let exported@{ id -> - val export = exportedNames[id] ?: return@exported - if (export.first != path) { - // This is expected only for split modules such as test extraction. - // If imported explicitly from another module, we expect to see a local name. - // The effect of directly importing and using the ExportedName is somewhat the same - // as if it had been directly in this module in terms of the name we use. - needs.putMultiSet(export.first, export.second) - // Keep out of importedBySomeOtherFile because already exported. - } - } - } - VisitCue.Continue - } - is Js.ImportSpecifier -> - // The exported name is not an ID that we need. - // We don't need the local name because we've already got it. - VisitCue.SkipOne - else -> VisitCue.Continue - } - } - val importsNeeded = needs.entries - val importPos = program.pos.leftEdge - val importDeclarations = importsNeeded.mapNotNull imports@{ (fileToImportFrom, namesToImport) -> - val pathParts = if (fileToImportFrom.segments.firstOrNull() == globalPathSegment) { - // Global references are handled through tmpl imports. - return@imports null - } else { - // Relative import. - val pathParts = path.relativePathTo(fileToImportFrom).toMutableList() - if (pathParts.getOrNull(0) !in relativeModulePathStarts) { - pathParts.add(index = 0, element = SameDirPseudoFilePathSegment) - } - pathParts - } - if (fileToImportFrom in importedBySomeOtherFile) { - // An internal version will be generated because someone is using non-publics. - // So just go to internals, whether the specifiers here are internal or not. - // Simplifies the bookkeeping. - pathParts[pathParts.lastIndex] = - (pathParts.last() as FilePathSegment).withExtension(INTERNAL_EXTENSION) - } - Js.ImportDeclaration( - importPos, - specifiers = listOf( - Js.ImportSpecifiers( - importPos, - namesToImport.sortedBy { it.text }.map { - Js.ImportSpecifier( - importPos, - imported = Js.Identifier( - importPos, - localNameToExportedName[it] ?: it, - null, - ), - local = Js.Identifier(importPos, it, null), - ) - }, - ), - ), - source = Js.StringLiteral( - importPos, - // TODO: this is probably not right. What is the actual rule for escaping - // path segments in the web-platform / Node / Deno worlds? - pathParts.join(separator = UNIX_FILE_SEGMENT_SEPARATOR, isDir = false), - ), - ) - } - program.topLevel = importDeclarations + program.topLevel - } - // Export from each according to their ability. - return translations.flatMap { translation -> - val program = translation.program - val exported = importedBySomeOtherFile[translation.outPath] - if (exported?.isNotEmpty() == true) { - val exportsInOrder = exported.sortedBy { it.text } - val exportPos = program.pos.rightEdge - val exportDeclaration = Js.ExportNamedDeclaration( - exportPos, - doc = Js.MaybeJsDocComment(exportPos, null), - declaration = null, - specifiers = exportsInOrder.map { exportedName -> - Js.ExportSpecifier( - exportPos, - local = Js.Identifier(exportPos, exportedName, null), - exported = Js.Identifier(exportPos, exportedName, null), - ) - }, - source = null, - ) - // Find already exported things to re-export from public face. - val exporteds = findTopLevelExportedIds(translation.program) - // Add new exports to internal, rename to internal, and add exporting public face. - program.topLevel += exportDeclaration - val internalOutPath = translation.outPath.withExtension(INTERNAL_EXTENSION)!! - listOf( - translation.copy(outPath = internalOutPath), - translation.copy(program = buildPublicFace(exportPos, exporteds, internalOutPath)), - ) - } else { - listOf(translation) - } - } - } - override val supportNetwork: SupportNetwork get() = JsSupportNetwork override fun wrapTokenSink(tokenSink: TokenSink): TokenSink = Companion.wrapTokenSink(tokenSink) @@ -691,7 +551,7 @@ private data class JsDependencies( } @Suppress("UnusedPrivateMember") -private fun JsDependencies.withDependency(dep: JsDependency): JsDependencies = // +private fun JsDependencies.withDependency(dep: JsDependency): JsDependencies = JsDependencies(this.runtimeDependencies + dep, this.testDependencies) private fun JsDependencies.withTestDependency(dep: JsDependency): JsDependencies = JsDependencies(this.runtimeDependencies, this.testDependencies + dep) @@ -781,49 +641,6 @@ private fun findDeclaredNames( -> emptyList() } -private fun findTopLevelExportedIds(program: Js.Program): List { - // And the dig here only goes through top levels rather than arbitrarily deep in the tree. - fun digId(tree: Js.Tree): List { - // TODO Dig out type aliases in comments? - return when (tree) { - is Js.ClassDeclaration -> listOf(tree.id) - is Js.DocumentedDeclaration -> digId(tree.decl) - is Js.ExportNamedDeclaration -> when (val declaration = tree.declaration) { - null -> tree.specifiers.map { it.exported } - else -> digId(declaration) - } - - is Js.FunctionDeclaration -> listOf(tree.id) - // Presume we don't generate destructuring for top levels. - is Js.VariableDeclaration -> tree.declarations.mapNotNull { it.id as? Js.Identifier } - else -> emptyList() - } - } - val exporteds = program.topLevel.flatMap { tree -> - val ids = digId(tree) - ids.mapNotNull { id -> - when (id.sourceIdentifier) { - is ExportedName -> id - else -> null - } - } - } - return exporteds -} - -private fun buildPublicFace(pos: Position, exporteds: List, internalOutPath: FilePath) = Js.Program( - pos, - listOf( - Js.ExportNamedDeclaration( - pos, - doc = Js.MaybeJsDocComment(pos, null), - declaration = null, - specifiers = exporteds.map { Js.ExportSpecifier(pos, it.deepCopy(), it.deepCopy()) }, - source = Js.StringLiteral(pos, "./${internalOutPath.last().fullName}"), - ), - ), -) - private fun FilePath.importReadyPath(): String = this.segments.importReadyPath(isDir = this.isDir) diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt index 774ba844..d50399a7 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt @@ -31,6 +31,7 @@ import lang.temper.format.CodeFormattingTemplate import lang.temper.format.toStringViaTokenSink import lang.temper.lexer.Genre import lang.temper.lexer.temperAwareBaseName +import lang.temper.log.FilePath import lang.temper.log.FilePath.Companion.join import lang.temper.log.FilePath.Companion.toPseudoPath import lang.temper.log.FilePathSegment @@ -137,6 +138,11 @@ internal class JsTranslator( private var libraryName: DashedIdentifier? = null + private val importedIds = mutableListOf() + private val exportedIds = mutableListOf() + private val importsFromProdToTest = mutableSetOf() + private val prodTopIds = mutableSetOf() + fun translate(t: TmpL.Module): List = jsNames.forOrigin(t.codeLocation.origin) { debug { console.log("$t") @@ -146,17 +152,21 @@ internal class JsTranslator( // Build imports before topLevels, or else local import names get mismatched. val ungroupedImports = mutableListOf() translateImports(t.imports, ungroupedImports) - t.topLevels.forEach topLevels@{ - val dependencyCategory = effectiveDependencyCategory(it) - when (dependencyCategory) { - DependencyCategory.Production -> prodModuleParts - DependencyCategory.Test -> testModuleParts - null -> return@topLevels - }.topLevels.addAll( - withDependencyMode(dependencyCategory) { - translateTopLevel(it) - }, - ) + // Translate prod first to gather top-level ids. + t.topLevels.forEach topLevels@{ topLevel -> + if (effectiveDependencyCategory(topLevel) == DependencyCategory.Production) { + withDependencyMode(DependencyCategory.Production) { + translateTopLevel(topLevel) + }.also { prodModuleParts.topLevels.addAll(it) } + } + } + // Then translate test so we track imports from prod. + t.topLevels.forEach topLevels@{ topLevel -> + if (effectiveDependencyCategory(topLevel) == DependencyCategory.Test) { + withDependencyMode(DependencyCategory.Test) { + translateTopLevel(topLevel) + }.also { testModuleParts.topLevels.addAll(it) } + } } val imports = run { @@ -212,6 +222,18 @@ internal class JsTranslator( } testModuleParts.explicitImports.addAll(testImports) + // Also import from prod to test. Technically could be empty, but meh. + val publicOutPath = t.codeLocation.outputPath + val internalOutPath = publicOutPath.withExtension(JsBackend.INTERNAL_EXTENSION)!! + Js.ImportDeclaration( + t.pos, + importsFromProdToTest.map { idName -> + val id = Js.Identifier(t.pos, idName, sourceIdentifier = null) + Js.ImportSpecifier(t.pos, id, id.deepCopy()) + }.let { listOf(Js.ImportSpecifiers(t.pos, it)) }, + Js.StringLiteral(t.pos, "../${internalOutPath.segments.last()}"), + ).also { testModuleParts.explicitImports.add(it) } + val result = t.result if (result != null) { prodModuleParts.topLevels.add( @@ -234,24 +256,28 @@ internal class JsTranslator( buildList { if (prodModuleParts.topLevels.isNotEmpty() || !hasTests) { - add( - Translation( - t.codeLocation.outputPath, - Js.Program(t.pos, adjustTops(prodModuleParts.topLevels)), - t, - DependencyCategory.Production, - ), - ) + // Internal file with everything exported, for tests and connecteds. + Translation( + internalOutPath, + Js.Program(t.pos, adjustTops(prodModuleParts.topLevels)), + t, + DependencyCategory.Production, + ).also { add(it) } + // Public face with just the actual exports. Add it even if no exports. + Translation( + publicOutPath, + buildPublicFace(t.pos, exportedIds, internalOutPath), + t, + DependencyCategory.Production, + ).also { add(it) } } if (hasTests) { - add( - Translation( - testPath, - Js.Program(t.pos, adjustTops(testModuleParts.topLevels)), - t, - DependencyCategory.Test, - ), - ) + Translation( + testPath, + Js.Program(t.pos, adjustTops(testModuleParts.topLevels)), + t, + DependencyCategory.Test, + ).also { add(it) } } } } @@ -366,6 +392,7 @@ internal class JsTranslator( continue // We don't need connected type imports } val exportName = JsIdentifierName.escaped(import.externalName.outName!!.outputNameText) + importedIds.add(exportName) val imported = Js.Identifier(import.pos, exportName, import.externalName.name) val local = import.localName?.let { translateId(it) as? Js.Identifier } val externalName = import.externalName.name @@ -864,11 +891,20 @@ internal class JsTranslator( * * @param useThisStack see [JsNames.withLocalNameForThis]. */ - private fun translateId(id: TmpL.Id, useThisStack: Boolean = false): Js.Expression = - when (genreTranslating) { + private fun translateId(id: TmpL.Id, useThisStack: Boolean = false): Js.Expression { + val result = when (genreTranslating) { Genre.Library -> translateIdForLibrary(id = id, useThisStack = useThisStack) Genre.Documentation -> translateIdForDocumentation(id = id, useThisStack = useThisStack) } + if (dependencyMode == DependencyCategory.Test && id.name in prodTopIds) { + result.simpleId()?.also { resultId -> + if (resultId.name !in importedIds) { + importsFromProdToTest.add(resultId.name) + } + } + } + return result + } private fun translateIdForLibrary(id: TmpL.Id, useThisStack: Boolean): Js.Expression { val name = id.name @@ -934,20 +970,25 @@ internal class JsTranslator( // TODO(mikesamuel): translateEnumType TmpL.TypeDeclarationKind.Enum -> translateTypeDeclaration(d, nameText) } - return if (d.name.name is ExportedName) { - val topLevelsWithExport = topLevels.toMutableList() - val toExport = topLevelsWithExport[mainDeclIndex] as Js.Declaration - topLevelsWithExport[mainDeclIndex] = Js.ExportNamedDeclaration( - pos = toExport.pos, - doc = Js.MaybeJsDocComment(toExport.pos.leftEdge, doc = null), - declaration = toExport, - specifiers = emptyList(), - source = null, - ) - topLevelsWithExport.toList() - } else { - topLevels - } + // Export all core top-levels from internal. + val topLevelsWithExport = topLevels.toMutableList() + val toExport = topLevelsWithExport[mainDeclIndex] as Js.Declaration + val toExportId = toExport.simpleId() + if (d.name.name is ExportedName) { + // But track those which are actually exported for the public module. + exportedIds.add(toExportId!!) + } + if (dependencyMode == DependencyCategory.Production) { + prodTopIds.add(d.name.name) + } + topLevelsWithExport[mainDeclIndex] = Js.ExportNamedDeclaration( + pos = toExport.pos, + doc = Js.MaybeJsDocComment(toExport.pos.leftEdge, doc = null), + declaration = toExport, + specifiers = emptyList(), + source = null, + ) + return topLevelsWithExport.toList() } private fun makeClassBuilder( @@ -1390,29 +1431,25 @@ internal class JsTranslator( originalName: TmpL.Id, ): Js.TopLevel { val name = originalName.name - return if (name is ExportedName && name.comesFrom(jsNames.origin)) { - val id = when (declaration) { - is Js.ClassDeclaration -> declaration.id - is Js.ExceptionDeclaration -> return declaration - is Js.FunctionDeclaration -> declaration.id - is Js.VariableDeclaration -> { - check(declaration.declarations.size == 1) - declaration.declarations.first().id as Js.Identifier - } - } - id.sourceIdentifier = name // Store so that we can link imports to exports later. - Js.ExportNamedDeclaration( - declaration.pos, - doc = doc, - declaration = declaration, - specifiers = emptyList(), - source = null, - ) - } else if (doc.doc != null) { - Js.DocumentedDeclaration(declaration.pos, doc.doc!!, declaration) - } else { - declaration - } + val id = declaration.simpleId() ?: return declaration + // Old comment: Store so that we can link imports to exports later. + // TODO Is sourceIdentifier really still in use? + id.sourceIdentifier = name + if (name is ExportedName && name.comesFrom(jsNames.origin)) { + // Only some are exported from the public module. + exportedIds.add(id) + } + if (dependencyMode == DependencyCategory.Production) { + prodTopIds.add(name) + } + // But internal exports all. + return Js.ExportNamedDeclaration( + declaration.pos, + doc = doc, + declaration = declaration, + specifiers = emptyList(), + source = null, + ) } private fun translateBoilerplateCodeFoldBoundary(t: TmpL.BoilerplateCodeFoldBoundary): Js.CommentLine { @@ -2406,3 +2443,42 @@ private val adaptAwaiter = JsExternalValueReference( DashedIdentifier.temperCoreLibraryIdentifier, JsIdentifierName("adaptAwaiter"), ) + +/** Returns null if no simple, single id. */ +internal fun Js.Declaration.simpleId(): Js.Identifier? { + return when (this) { + is Js.ClassDeclaration -> this.id + is Js.FunctionDeclaration -> this.id + is Js.ExceptionDeclaration -> this.id.simpleId() + is Js.VariableDeclaration -> when (declarations.size) { + 1 -> declarations.first().id.simpleId() + else -> null + } + } +} + +/** Returns null if this isn't a simple, single id. */ +internal fun Js.Expression.simpleId(): Js.Identifier? { + return this as? Js.Identifier +} + +/** Returns null if this isn't a simple, single id. */ +internal fun Js.Pattern.simpleId(): Js.Identifier? { + return when (this) { + is Js.Identifier -> this + else -> null + } +} + +private fun buildPublicFace(pos: Position, exporteds: List, internalOutPath: FilePath) = Js.Program( + pos, + listOf( + Js.ExportNamedDeclaration( + pos, + doc = Js.MaybeJsDocComment(pos, null), + declaration = null, + specifiers = exporteds.map { Js.ExportSpecifier(pos, it.deepCopy(), it.deepCopy()) }, + source = Js.StringLiteral(pos, "./${internalOutPath.last().fullName}"), + ), + ), +) diff --git a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt index 7de7626d..f06d4b1a 100644 --- a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt +++ b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt @@ -32,9 +32,7 @@ class JsBackendTest { | "foo.js": { | content: | ``` - | /** @type {number} */ - | const return_2 = 123; - | export default return_2; + | export {} from "./foo.internal.js"; | | ```, | mimeType: "text/javascript", @@ -46,9 +44,31 @@ class JsBackendTest { | file: "js/my-test-library/src/foo.js", | sources: ["src/foo/foo.temper"], | sourcesContent: ["123"], + | names: [], + | // Haven't checked. + | mappings: "AAAG,cAAA,AAAH,oBAAG", + | }, + | }, + | "foo.internal.js": { + | content: + | ``` + | /** @type {number} */ + | export const return_2 = 123; + | export default return_2; + | + | ```, + | mimeType: "text/javascript", + | }, + | "foo.internal.js.map": { + | mimeType: "application/json", + | jsonContent: { + | version: 3, + | file: "js/my-test-library/src/foo.internal.js", + | sources: ["src/foo/foo.temper"], + | sourcesContent: ["123"], | names: ["return"], | // Haven't checked. - | mappings: "AAAA;AAAA,MAAAA,QAAA,MAAG,AAAH;AAAG,eAAAA,QAAA", + | mappings: "AAAA;AAAA,aAAAA,QAAA,MAAG,AAAH;AAAG,eAAAA,QAAA", | }, | } | }, From 90f64a2404e5c354471b2774ad74e92777c5b4c4 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 27 Jul 2026 16:19:51 -0700 Subject: [PATCH 02/12] Pass tests on full internal Signed-off-by: Tom --- .../kotlin/lang/temper/be/js/JsBackend.kt | 23 +- .../kotlin/lang/temper/be/js/JsTranslator.kt | 79 +++-- .../kotlin/lang/temper/be/js/JsBackendTest.kt | 330 ++++++++++-------- .../lang/temper/be/js/JsTranslatorTest.kt | 95 ++++- 4 files changed, 323 insertions(+), 204 deletions(-) diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt index af3fa7f7..ec0d5975 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt @@ -38,6 +38,7 @@ import lang.temper.log.SameDirPseudoFilePathSegment import lang.temper.log.UNIX_FILE_SEGMENT_SEPARATOR import lang.temper.log.dirPath import lang.temper.log.filePath +import lang.temper.log.last import lang.temper.log.unknownPos import lang.temper.name.BackendId import lang.temper.name.BackendMeta @@ -278,9 +279,15 @@ class JsBackend private constructor( it.dependencyCategory == DependencyCategory.Production } val exports = mutableMapOf() - // Export all modules. - for (translation in exportingTranslations) { - exports[translation.outPath.exportPath()] = translation.outPath + // Export all public modules. + translations@ for (translation in exportingTranslations) { + val outPath = translation.outPath + val outName = outPath.last().fullName + // Skip internal modules. + outName.endsWith(INTERNAL_EXTENSION) && continue@translations + outName.startsWith("_") && continue@translations + // Export others without js extension. + exports[outPath.exportPath()] = outPath } // Also a main to init everything, and just call it "index.js". // It's responsible for loading the submodules and re-exporting @@ -350,11 +357,7 @@ class JsBackend private constructor( val updatedSpecifiers = imported.specifiers.flatMap { specifier -> with(specifier.local.name.text) { when { - startsWith("test_") -> { - itName = DeclarationInfo(specifier.pos, specifier.local) - listOf() - } - + startsWith("test_") -> listOf() else -> listOf(specifier) } } @@ -534,10 +537,6 @@ internal fun walkDepthFirst(t: Js.Tree, action: (Js.Tree) -> VisitCue): VisitCue return VisitCue.Continue } -/** An ES-modules file-like module path is one that starts with "/", "./", "../". */ -private val relativeModulePathStarts = - setOf(SameDirPseudoFilePathSegment, ParentPseudoFilePathSegment) - private data class JsDependency( val name: String, val versionString: String, diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt index d50399a7..755438da 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt @@ -45,6 +45,7 @@ import lang.temper.log.spanningPosition import lang.temper.name.BuiltinName import lang.temper.name.DashedIdentifier import lang.temper.name.ExportedName +import lang.temper.name.ModularName import lang.temper.name.ParsedName import lang.temper.name.ResolvedName import lang.temper.name.ResolvedParsedName @@ -970,25 +971,27 @@ internal class JsTranslator( // TODO(mikesamuel): translateEnumType TmpL.TypeDeclarationKind.Enum -> translateTypeDeclaration(d, nameText) } - // Export all core top-levels from internal. - val topLevelsWithExport = topLevels.toMutableList() - val toExport = topLevelsWithExport[mainDeclIndex] as Js.Declaration - val toExportId = toExport.simpleId() - if (d.name.name is ExportedName) { - // But track those which are actually exported for the public module. - exportedIds.add(toExportId!!) - } - if (dependencyMode == DependencyCategory.Production) { + // Export all prod top-levels from internal. + return if (dependencyMode == DependencyCategory.Production) { + val topLevelsWithExport = topLevels.toMutableList() + val toExport = topLevelsWithExport[mainDeclIndex] as Js.Declaration + val toExportId = toExport.simpleId() + if (d.name.name is ExportedName) { + // But track those which are actually exported for the public module. + exportedIds.add(toExportId!!) + } prodTopIds.add(d.name.name) + topLevelsWithExport[mainDeclIndex] = Js.ExportNamedDeclaration( + pos = toExport.pos, + doc = Js.MaybeJsDocComment(toExport.pos.leftEdge, doc = null), + declaration = toExport, + specifiers = emptyList(), + source = null, + ) + topLevelsWithExport.toList() + } else { + topLevels } - topLevelsWithExport[mainDeclIndex] = Js.ExportNamedDeclaration( - pos = toExport.pos, - doc = Js.MaybeJsDocComment(toExport.pos.leftEdge, doc = null), - declaration = toExport, - specifiers = emptyList(), - source = null, - ) - return topLevelsWithExport.toList() } private fun makeClassBuilder( @@ -1431,25 +1434,33 @@ internal class JsTranslator( originalName: TmpL.Id, ): Js.TopLevel { val name = originalName.name - val id = declaration.simpleId() ?: return declaration - // Old comment: Store so that we can link imports to exports later. - // TODO Is sourceIdentifier really still in use? - id.sourceIdentifier = name - if (name is ExportedName && name.comesFrom(jsNames.origin)) { - // Only some are exported from the public module. - exportedIds.add(id) - } - if (dependencyMode == DependencyCategory.Production) { + val id = declaration.simpleId() + return if ( + id != null + && dependencyMode == DependencyCategory.Production + && name is ModularName + && name.comesFrom(jsNames.origin) + ) { + // TODO Is sourceIdentifier really still in use? + id.sourceIdentifier = name // Store so that we can link imports to exports later. + if (name is ExportedName && name.comesFrom(jsNames.origin)) { + // Only some are exported from the public module. + exportedIds.add(id) + } prodTopIds.add(name) + // But internal exports all. + Js.ExportNamedDeclaration( + declaration.pos, + doc = doc, + declaration = declaration, + specifiers = emptyList(), + source = null, + ) + } else if (doc.doc != null) { + Js.DocumentedDeclaration(declaration.pos, doc.doc!!, declaration) + } else { + declaration } - // But internal exports all. - return Js.ExportNamedDeclaration( - declaration.pos, - doc = doc, - declaration = declaration, - specifiers = emptyList(), - source = null, - ) } private fun translateBoilerplateCodeFoldBoundary(t: TmpL.BoilerplateCodeFoldBoundary): Js.CommentLine { diff --git a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt index f06d4b1a..4e89ef80 100644 --- a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt +++ b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt @@ -104,10 +104,14 @@ class JsBackendTest { | "src": { | "foo.js": "__DO_NOT_CARE__", | "foo.js.map": "__DO_NOT_CARE__", + | "foo.internal.js": "__DO_NOT_CARE__", + | "foo.internal.js.map": "__DO_NOT_CARE__", | }, |## Top level module translated. | "my_test_library.js": "__DO_NOT_CARE__", | "my_test_library.js.map": "__DO_NOT_CARE__", + | "my_test_library.internal.js": "__DO_NOT_CARE__", + | "my_test_library.internal.js.map": "__DO_NOT_CARE__", |## The generated index.js should load the modules in order and re-export any top-level module. | "index.js": { | content: ``` @@ -157,12 +161,22 @@ class JsBackendTest { | "foo.js": { | content: | ``` + | export { + | i + | } from "./foo.internal.js"; + | + | ``` + | }, + | "foo.internal.js": { + | content: + | ``` | /** @type {number} */ | export const i = 0; | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__" + | "foo.js.map": "__DO_NOT_CARE__", + | "foo.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -195,19 +209,21 @@ class JsBackendTest { | "js": { | "my-test-library": { | "src": { - | "foo.js": { + | "foo.internal.js": { | "content": | ``` | /** @type {number} */ - | let one_0 = 1; + | export let one_0 = 1; | one_0 = one_0; | /** @type {number} */ - | const return_0 = one_0 + one_0 | 0; + | export const return_0 = one_0 + one_0 | 0; | export default return_0; | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__" + | "foo.js": "__DO_NOT_CARE__", + | "foo.js.map": "__DO_NOT_CARE__", + | "foo.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -257,12 +273,12 @@ class JsBackendTest { | globalConsole as globalConsole_0 | } from "@temperlang/core"; | /** @type {Console_0} */ - | const console_0 = globalConsole_0; + | export const console_0 = globalConsole_0; | /** | * @param {number} i_0 | * @returns {number} | */ - | function fib_0(i_0) { + | export function fib_0(i_0) { | let t_0 = i_0.toString(); | console_0.log(t_0); | let a_0 = 0; @@ -274,7 +290,7 @@ class JsBackendTest { | i_0 = i_0 - 1 | 0; | } | return a_0; - | } + | }; | /** | * @param {number} i_1 | * @returns {number} @@ -282,9 +298,6 @@ class JsBackendTest { | export function fibber(i_1) { | return fib_0(i_1); | }; - | export { - | fib_0 - | }; | | ``` | }, @@ -304,11 +317,11 @@ class JsBackendTest { | "content": | ``` | import { - | fib_0 - | } from "../../src/fib.internal.js"; - | import { | Test as Test_0 | } from "@temperlang/std/testing"; + | import { + | fib_0 + | } from "../fib.internal.js"; | it("fib", function () { | const test_0 = new Test_0(); | try { @@ -372,11 +385,8 @@ class JsBackendTest { | globalConsole as globalConsole_0 | } from "@temperlang/core"; | /** @type {Console_0} */ - | const console_0 = globalConsole_0; + | export const console_0 = globalConsole_0; | console_0.log("Here be side effects."); - | export { - | console_0 - | }; | | ``` | }, @@ -394,11 +404,11 @@ class JsBackendTest { | "content": | ``` | import { - | console_0 - | } from "../sub.internal.js"; - | import { | Test as Test_0 | } from "@temperlang/std/testing"; + | import { + | console_0 + | } from "../sub.internal.js"; | /** @param {string} name_0 */ | function greet_0(name_0) { | console_0.log("Hi, " + name_0 + "!"); @@ -450,16 +460,16 @@ class JsBackendTest { | js: { | "my-test-library": { | src: { - | "Brahmagupta'sRevenge.js": { + | "Brahmagupta'sRevenge.internal.js": { | content: | ``` | import { | divIntInt as divIntInt_0 | } from "@temperlang/core"; | /** @type {number} */ - | let return_0; + | export let return_0; | /** @type {number} */ - | let t_0; + | export let t_0; | try { | t_0 = divIntInt_0(0, 0); | return_0 = t_0; @@ -470,7 +480,9 @@ class JsBackendTest { | | ``` | }, + | "Brahmagupta'sRevenge.js": "__DO_NOT_CARE__", | "Brahmagupta'sRevenge.js.map": "__DO_NOT_CARE__", + | "Brahmagupta'sRevenge.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -504,7 +516,7 @@ class JsBackendTest { | js: { | "my-test-library": { | "src": { - | "C.js": { + | "C.internal.js": { | content: | ``` | import { @@ -553,7 +565,9 @@ class JsBackendTest { | | ```, | }, + | "C.js": "__DO_NOT_CARE__", | "C.js.map": "__DO_NOT_CARE__", + | "C.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -588,7 +602,7 @@ class JsBackendTest { | "js": { | "my-test-library": { | "src": { - | "C.js": { + | "C.internal.js": { | "content": | ``` | import { @@ -620,7 +634,9 @@ class JsBackendTest { | | ``` | }, - | "C.js.map": "__DO_NOT_CARE__" + | "C.js": "__DO_NOT_CARE__", + | "C.js.map": "__DO_NOT_CARE__", + | "C.internal.js.map": "__DO_NOT_CARE__", | }, | "package.json": "__DO_NOT_CARE__", | "index.js": "__DO_NOT_CARE__" @@ -650,7 +666,7 @@ class JsBackendTest { | "js": { | "my-test-library": { | "src": { - | "eq.js": { + | "eq.internal.js": { | "content": | ``` | /** @@ -663,7 +679,9 @@ class JsBackendTest { | | ``` | }, - | "eq.js.map": "__DO_NOT_CARE__" + | "eq.js": "__DO_NOT_CARE__", + | "eq.js.map": "__DO_NOT_CARE__", + | "eq.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -691,17 +709,17 @@ class JsBackendTest { | "js": { | "my-test-library": { | "src": { - | "list.js": { - | "_name": "list.js", - | "_type": "txt", + | "list.internal.js": { | "content": ``` | /** @type {Array} */ - | const return_0 = Object.freeze([3, 4]); + | export const return_0 = Object.freeze([3, 4]); | export default return_0; | | ``` | }, - | "list.js.map": "__DO_NOT_CARE__" + | "list.js": "__DO_NOT_CARE__", + | "list.js.map": "__DO_NOT_CARE__", + | "list.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -731,17 +749,17 @@ class JsBackendTest { | "js": { | "my-test-library": { | "src": { - | "list.js": { - | "_name": "list.js", - | "_type": "txt", + | "list.internal.js": { | "content": ``` | /** @type {Array} */ - | const return_0 = Object.freeze(["", "foo", '"', "'", "bold<\/b>", "foo\n\\bar\r\n.baz", "\x00"]); + | export const return_0 = Object.freeze(["", "foo", '"', "'", "bold<\/b>", "foo\n\\bar\r\n.baz", "\x00"]); | export default return_0; | | ``` | }, - | "list.js.map": "__DO_NOT_CARE__" + | "list.js": "__DO_NOT_CARE__", + | "list.js.map": "__DO_NOT_CARE__", + | "list.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -771,43 +789,40 @@ class JsBackendTest { |} """.trimMargin(), ), - moduleResultNeeded = true, want = """ |{ | js: { | "my-test-library": { | src: { - | "a.js": { + | "a.internal.js": { | content: ``` | import { | globalConsole as globalConsole_0 | } from "@temperlang/core"; | /** @type {Console_0} */ - | const console_0 = globalConsole_0; + | export const console_0 = globalConsole_0; | export function f() { | console_0.log("f"); | return; | }; - | /** @type {void} */ - | const return_1 = void 0; - | export default return_1; | | ```, | }, - | "b.js": { + | "b.internal.js": { | content: ``` | import { | f as f_0 | } from "./a.js"; | f_0(); - | /** @type {void} */ - | const return_2 = void 0; - | export default return_2; | | ```, | }, + | "a.js": "__DO_NOT_CARE__", + | "b.js": "__DO_NOT_CARE__", | "a.js.map": "__DO_NOT_CARE__", | "b.js.map": "__DO_NOT_CARE__", + | "a.internal.js.map": "__DO_NOT_CARE__", + | "b.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -841,7 +856,16 @@ class JsBackendTest { |{ | "js": { | "my-test-library": { - | a.js: { + | "a.js": { + | content: + | ``` + | export { + | C + | } from "./a.internal.js"; + | + | ``` + | }, + | a.internal.js: { | content: | ``` | import { @@ -850,7 +874,7 @@ class JsBackendTest { | import { | type as type_0, requireInstanceOf as requireInstanceOf_0, marshalToJsonObject as marshalToJsonObject_0 | } from "@temperlang/core"; - | class CJsonAdapter_0 extends type_0() { + | export class CJsonAdapter_0 extends type_0() { | /** | * @param {C} x_0 | * @param {JsonProducer_0} p_0 @@ -871,7 +895,7 @@ class JsBackendTest { | super (); | return; | } - | } + | }; | export class C extends type_0() { | constructor() { | super (); @@ -906,6 +930,7 @@ class JsBackendTest { | ``` | }, | "a.js.map": "__DO_NOT_CARE__", + | "a.internal.js.map": "__DO_NOT_CARE__", | $OUTPUT_BOILERPLATE | }, | } @@ -930,13 +955,14 @@ class JsBackendTest { |} """.trimMargin(), ), + // Result somewhat interesting for function type. moduleResultNeeded = true, want = """ |{ | "js": { | "my-test-library": { | "src": { - | "strings.js": { + | "strings.internal.js": { | "content": | ``` | import { @@ -946,17 +972,19 @@ class JsBackendTest { | * @param {string} s_0 | * @returns {boolean} | */ - | function f_0(s_0) { + | export function f_0(s_0) { | return stringSplit_0(s_0, ",").length === 1; - | } + | }; | /** @type {(arg0: string) => boolean} */ - | const return_1 = f_0; + | export const return_1 = f_0; | export default return_1; | | ```, | "mimeType": "text/javascript" | }, - | "strings.js.map": "__DO_NOT_CARE__" + | "strings.js": "__DO_NOT_CARE__", + | "strings.js.map": "__DO_NOT_CARE__", + | "strings.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -988,7 +1016,7 @@ class JsBackendTest { | "js": { | "my-test-library": { | "src": { - | "props.js": { + | "props.internal.js": { | "content": | ``` | import { @@ -1045,7 +1073,9 @@ class JsBackendTest { | ```, | "mimeType": "text/javascript" | }, - | "props.js.map": "__DO_NOT_CARE__" + | "props.js": "__DO_NOT_CARE__", + | "props.js.map": "__DO_NOT_CARE__", + | "props.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -1075,19 +1105,19 @@ class JsBackendTest { | "js": { | "my-test-library": { | "src": { - | "skipped.js": { + | "skipped.internal.js": { | "content": | ``` | import { | globalConsole as globalConsole_0 | } from "@temperlang/core"; | /** @type {Console_0} */ - | const console_0 = globalConsole_0; + | export const console_0 = globalConsole_0; | /** | * @param {number | null} [a_0] | * @param {number | null} [b_0] | */ - | function hi_0(a_0, b_0) { + | export function hi_0(a_0, b_0) { | let a_1; | if (a_0 == null) { | a_1 = 1; @@ -1103,13 +1133,15 @@ class JsBackendTest { | let t_0 = (a_1 + b_1 | 0).toString(); | console_0.log(t_0); | return; - | } + | }; | hi_0(null, 3); | | ```, | "mimeType": "text/javascript" | }, - | "skipped.js.map": "__DO_NOT_CARE__" + | "skipped.js": "__DO_NOT_CARE__", + | "skipped.js.map": "__DO_NOT_CARE__", + | "skipped.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -1139,21 +1171,23 @@ class JsBackendTest { | js: { | "my-test-library": { | src: { - | "newDeque.js": { + | "newDeque.internal.js": { | content: | ``` | import { | dequeConstructor as dequeConstructor_0 | } from "@temperlang/core"; | /** @type {Deque_0} */ - | const x_0 = dequeConstructor_0(); + | export const x_0 = dequeConstructor_0(); | /** @type {Deque_0} */ - | const y_0 = dequeConstructor_0(); + | export const y_0 = dequeConstructor_0(); | | ```, | mimeType: "text/javascript" | }, + | "newDeque.js": "__DO_NOT_CARE__", | "newDeque.js.map": "__DO_NOT_CARE__", + | "newDeque.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -1182,7 +1216,7 @@ class JsBackendTest { | js: { | "my-test-library": { | src: { - | "logs.js": { + | "logs.internal.js": { | content: | ``` | import { @@ -1193,7 +1227,9 @@ class JsBackendTest { | ```, | mimeType: "text/javascript" | }, + | "logs.js": "__DO_NOT_CARE__", | "logs.js.map": "__DO_NOT_CARE__", + | "logs.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -1228,7 +1264,7 @@ class JsBackendTest { |{ | js: { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: ``` | import { | type as type_0 @@ -1262,8 +1298,7 @@ class JsBackendTest { | | ```, | }, - | "foo.js.map": "__DO_NOT_CARE__", - | "bar.js": { + | "bar.internal.js": { | content: ``` | import { | Point as Point_0 @@ -1273,8 +1308,10 @@ class JsBackendTest { | | ```, | }, + | "bar.js": "__DO_NOT_CARE__", | "bar.js.map": "__DO_NOT_CARE__", - |$OUTPUT_BOILERPLATE + | "bar.internal.js.map": "__DO_NOT_CARE__", + |$BONUS_FOO_BOILERPLATE | } | } |} @@ -1300,23 +1337,22 @@ class JsBackendTest { |{ | js: { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: ``` | /** @type {number} */ - | let m_0 = 0; + | export let m_0 = 0; | /** @returns {number | null} */ - | function f_0() { + | export function f_0() { | m_0 = 5; | return m_0; - | } + | }; | /** @type {number | null} */ | export let n; | n = f_0(); | | ```, | }, - | "foo.js.map": "__DO_NOT_CARE__", - |$OUTPUT_BOILERPLATE + |$BONUS_FOO_BOILERPLATE | } | } |} @@ -1341,7 +1377,7 @@ class JsBackendTest { |{ | js: { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: ``` | /** @returns {void} */ | export function f() { @@ -1351,8 +1387,7 @@ class JsBackendTest { | | ```, | }, - | "foo.js.map": "__DO_NOT_CARE__", - |$OUTPUT_BOILERPLATE + |$BONUS_FOO_BOILERPLATE | } | } |} @@ -1377,7 +1412,7 @@ class JsBackendTest { |{ | js: { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: ``` | ((() => { | throw "howAboutThis not available from core"; @@ -1385,8 +1420,7 @@ class JsBackendTest { | | ```, | }, - | "foo.js.map": "__DO_NOT_CARE__", - |$OUTPUT_BOILERPLATE + |$BONUS_FOO_BOILERPLATE | } | }, | errors: [ @@ -1414,7 +1448,7 @@ class JsBackendTest { |{ | js: { | "my-test-library": { - | "div-example.js": { + | "div-example.internal.js": { | content: ``` | /** | * @param {number} n_0 @@ -1426,7 +1460,9 @@ class JsBackendTest { | | ```, | }, + | "div-example.js": "__DO_NOT_CARE__", | "div-example.js.map": "__DO_NOT_CARE__", + | "div-example.internal.js.map": "__DO_NOT_CARE__", |$OUTPUT_BOILERPLATE | } | } @@ -1453,7 +1489,7 @@ class JsBackendTest { |{ | "js": { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: | ``` | import { @@ -1472,8 +1508,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - |$OUTPUT_BOILERPLATE + |$BONUS_FOO_BOILERPLATE | } | }, |} @@ -1499,7 +1534,7 @@ class JsBackendTest { |{ | js: { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: | // If we generated an import any time there was a dependency, | // even one that was satisfied by connecting to a JS builtin, @@ -1511,8 +1546,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - |$OUTPUT_BOILERPLATE + |$BONUS_FOO_BOILERPLATE | } | } |} @@ -1557,13 +1591,13 @@ class JsBackendTest { | js: { | "my-test-library": { | src: { - | "a.js": { + | "a.internal.js": { | content: ``` | import { | globalConsole as globalConsole_0, type as type_0 | } from "@temperlang/core"; | /** @type {Console_0} */ - | const console_0 = globalConsole_0; + | export const console_0 = globalConsole_0; | export class catch_ extends type_0() { | /** @type {string} */ | #if_0; @@ -1588,12 +1622,12 @@ class JsBackendTest { | return; | }; | /** @type {catch_} */ - | const in_0 = new catch_("something"); + | export const in_0 = new catch_("something"); | console_0.log(in_0.if_); | | ```, | }, - | "b.js": { + | "b.internal.js": { | content: ``` | import { | switch_ as switch_0, catch_ as catch_0 @@ -1605,8 +1639,12 @@ class JsBackendTest { | | ```, | }, + | "a.js": "__DO_NOT_CARE__", + | "b.js": "__DO_NOT_CARE__", | "a.js.map": "__DO_NOT_CARE__", | "b.js.map": "__DO_NOT_CARE__", + | "a.internal.js.map": "__DO_NOT_CARE__", + | "b.internal.js.map": "__DO_NOT_CARE__", | }, |$OUTPUT_BOILERPLATE | } @@ -1637,20 +1675,20 @@ class JsBackendTest { |{ | js: { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: | ``` | import { | globalConsole as globalConsole_0, PromiseBuilder as PromiseBuilder_0, adaptAwaiter as adaptAwaiter_0, panic as panic_0, runAsync as runAsync_0 | } from "@temperlang/core"; | /** @type {Console_0} */ - | const console_0 = globalConsole_0; + | export const console_0 = globalConsole_0; | /** @type {PromiseBuilder_0} */ - | const b_0 = new PromiseBuilder_0(); + | export const b_0 = new PromiseBuilder_0(); | /** @type {globalThis.Promise} */ - | const p_0 = b_0.promise; + | export const p_0 = b_0.promise; | /** @returns {Generator<{}>} */ - | const fn_0 = adaptAwaiter_0(function* fn_0(await_0) { + | export const fn_0 = adaptAwaiter_0(function* fn_0(await_0) { | let t_0; | let t_1; | try { @@ -1666,8 +1704,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - |$OUTPUT_BOILERPLATE + |$BONUS_FOO_BOILERPLATE | } | } |} @@ -1697,16 +1734,16 @@ class JsBackendTest { |{ | js: { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: | ``` | import { | globalConsole as globalConsole_0 | } from "@temperlang/core"; | /** @type {Console_0} */ - | let t_0 = globalConsole_0; + | export let t_0 = globalConsole_0; | /** @type {globalThis.Array} */ - | const sb_0 = [""]; + | export const sb_0 = [""]; | void (sb_0[0] += "Hello, "); | void (sb_0[0] = ""); | void (sb_0[0] += "World"); @@ -1715,8 +1752,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - |$OUTPUT_BOILERPLATE + |$BONUS_FOO_BOILERPLATE | } | } |} @@ -1743,7 +1779,7 @@ class JsBackendTest { |{ | js: { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: | ``` | import { @@ -1759,8 +1795,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - |$OUTPUT_BOILERPLATE + |$BONUS_FOO_BOILERPLATE | } | } |} @@ -1795,7 +1830,7 @@ class JsBackendTest { |{ | js: { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: | ``` | import { @@ -1805,9 +1840,9 @@ class JsBackendTest { | globalConsole as globalConsole_0, adaptAwaiter as adaptAwaiter_0, netResponseGetStatus as netResponseGetStatus_0, netResponseGetBodyContent as netResponseGetBodyContent_0, netResponseGetContentType as netResponseGetContentType_0, runAsync as runAsync_0 | } from "@temperlang/core"; | /** @type {Console_0} */ - | const console_0 = globalConsole_0; + | export const console_0 = globalConsole_0; | /** @returns {Generator<{}>} */ - | const fn_0 = adaptAwaiter_0(function* fn_0(await_0) { + | export const fn_0 = adaptAwaiter_0(function* fn_0(await_0) { | let t_0; | let t_1; | let t_2; @@ -1842,8 +1877,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - |$OUTPUT_BOILERPLATE + |$BONUS_FOO_BOILERPLATE | } | } |} @@ -1870,7 +1904,7 @@ class JsBackendTest { |{ | "js": { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: ``` | const { | imul: imul_0 @@ -1925,8 +1959,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - | $OUTPUT_BOILERPLATE + | $BONUS_FOO_BOILERPLATE | }, | } |} @@ -1959,13 +1992,13 @@ class JsBackendTest { |{ | "js": { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: ``` | import { | globalConsole as globalConsole_0 | } from "@temperlang/core"; | /** @type {Console_0} */ - | const console_0 = globalConsole_0; + | export const console_0 = globalConsole_0; | /** @param {DenseBitVector_0 | null} x_0 */ | export function f(x_0) { | let t_0; @@ -1984,8 +2017,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - | $OUTPUT_BOILERPLATE + | $BONUS_FOO_BOILERPLATE | }, | } |} @@ -2010,21 +2042,23 @@ class JsBackendTest { |{ | js: { | my-test-library: { - | casty.js: { + | casty.internal.js: { | content: ``` | import { | requireIsArray as requireIsArray_0 | } from "@temperlang/core"; | /** @type {Array} */ - | const lb_0 = []; + | export const lb_0 = []; | /** @type {Array} */ | export let listed; | listed = requireIsArray_0(lb_0); | | ```, | }, + | casty.js: "__DO_NOT_CARE__", | casty.js.map: "__DO_NOT_CARE__", - | index.js: "__DO_NOT_CARE__", + | casty.internal.js.map: "__DO_NOT_CARE__", + | index.js: "__DO_NOT_CARE__", | package.json: "__DO_NOT_CARE__", | } | } @@ -2055,7 +2089,7 @@ class JsBackendTest { |{ | "js": { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: ``` | import { | requireStringIndex as requireStringIndex_0 @@ -2076,8 +2110,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - | $OUTPUT_BOILERPLATE + | $BONUS_FOO_BOILERPLATE | }, | } |} @@ -2108,7 +2141,7 @@ class JsBackendTest { |{ | "js": { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: ``` | import { | type as type_0 @@ -2142,8 +2175,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - | $OUTPUT_BOILERPLATE + | $BONUS_FOO_BOILERPLATE | }, | } |} @@ -2172,7 +2204,7 @@ class JsBackendTest { |{ | "js": { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: ``` | import { | type as type_0 @@ -2206,8 +2238,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - | $OUTPUT_BOILERPLATE + | $BONUS_FOO_BOILERPLATE | }, | } |} @@ -2235,7 +2266,7 @@ class JsBackendTest { |{ | "js": { | "my-test-library": { - | "foo.js": { + | "foo.internal.js": { | content: ``` | import { | type as type_0 @@ -2265,8 +2296,7 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__", - | $OUTPUT_BOILERPLATE + | $BONUS_FOO_BOILERPLATE | }, | } |} @@ -2302,7 +2332,7 @@ class JsBackendTest { |{ | js: { | my-test-library: { - | foo.js: { + | foo.internal.js: { | content: | ``` | /** @@ -2339,9 +2369,7 @@ class JsBackendTest { | | ``` | }, - | foo.js.map: "__DO_NOT_CARE__", - | index.js: "__DO_NOT_CARE__", - | package.json: "__DO_NOT_CARE__", + | $BONUS_FOO_BOILERPLATE | } | } |} @@ -2368,7 +2396,7 @@ class JsBackendTest { | "js": { | "my-test-library": { | "src": { - | "foo.js": { + | "foo.internal.js": { | "content": | ``` | /** @type {string} */ @@ -2376,7 +2404,9 @@ class JsBackendTest { | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__" + | "foo.js": "__DO_NOT_CARE__", + | "foo.js.map": "__DO_NOT_CARE__", + | "foo.internal.js.map": "__DO_NOT_CARE__", | }, | "package.json": "__DO_NOT_CARE__", | "index.js": "__DO_NOT_CARE__", @@ -2406,14 +2436,16 @@ class JsBackendTest { | "js": { | "my-test-library": { | "src": { - | "foo.js": { + | "foo.internal.js": { | "content": | ``` | [""][0].length; | | ``` | }, - | "foo.js.map": "__DO_NOT_CARE__" + | "foo.js": "__DO_NOT_CARE__", + | "foo.js.map": "__DO_NOT_CARE__", + | "foo.internal.js.map": "__DO_NOT_CARE__", | }, | "package.json": "__DO_NOT_CARE__", | "index.js": "__DO_NOT_CARE__", @@ -2427,3 +2459,11 @@ class JsBackendTest { private const val OUTPUT_BOILERPLATE = """ "package.json": "__DO_NOT_CARE__", "index.js": "__DO_NOT_CARE__", """ + +/** One very common case. */ +private const val BONUS_FOO_BOILERPLATE = """ + "foo.js": "__DO_NOT_CARE__", + "foo.js.map": "__DO_NOT_CARE__", + "foo.internal.js.map": "__DO_NOT_CARE__", + $OUTPUT_BOILERPLATE +""" diff --git a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsTranslatorTest.kt b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsTranslatorTest.kt index a9a9f486..fb14e931 100644 --- a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsTranslatorTest.kt +++ b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsTranslatorTest.kt @@ -31,44 +31,75 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor companion object { private val testNameToExpectedCode = mapOf( - "moduleMinimal" to "", + "moduleMinimal" to """ + | + |export {} from "./implement.internal.js"; + """.trimMargin(), "moduleWithImport" to """ |import { | frobnicate as frobnicate_0, lunarWayneshaft as lunarWayneshaft_1 |} from "./other.js"; + | + |export {} from "./implement.internal.js"; """.trimMargin(), "moduleWithTopLevel" to """ |/** @type {string} */ - |const exampleName_0 = "example assigned value"; + |export const exampleName_0 = "example assigned value"; + | + |export {} from "./implement.internal.js"; """.trimMargin(), - "moduleWithResult" to """export default "example module result";""", - "importNoLocalName" to "", + "moduleWithResult" to """ + |export default "example module result"; + | + |export {} from "./implement.internal.js"; + """.trimMargin(), + "importNoLocalName" to """ + | + |export {} from "./implement.internal.js"; + """.trimMargin(), "importOne" to """ |import { | pi as pi_0 |} from "./other.js"; + | + |export {} from "./implement.internal.js"; """.trimMargin(), "importThree" to """ |import { | math as math_0, pieCharts as pieCharts_1, magic as magic_2 |} from "./other.js"; + | + |export {} from "./implement.internal.js"; """.trimMargin(), - "expressionStatement" to "42;", - "assignmentToValue" to """maybeValue_0 = false;""", + "expressionStatement" to + """ + |42; + | + |export {} from "./implement.internal.js"; + """.trimMargin(), + "assignmentToValue" to """ + |maybeValue_0 = false; + | + |export {} from "./implement.internal.js"; + """.trimMargin(), "blockStatementEmpty" to """ |if (true) { |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "blockStatementOne" to """ |if (true) { | doThing_0("one"); |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "blockStatementThree" to """ @@ -77,6 +108,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | doThing_0("two"); | doThing_0("three"); |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "blockStatementBreaking" to """ @@ -86,6 +119,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | break label_0; | doThing_1("three"); |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileReturnEarly" to """ @@ -102,6 +137,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | afterLoop_6(); | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileReturnEarlySimple" to """ @@ -113,6 +150,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | } | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileBreakEarly" to """ @@ -129,6 +168,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | afterLoop_6(); | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileBreakEarlySimple" to """ @@ -140,6 +181,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | } | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileBreakNested" to """ @@ -160,6 +203,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | afterOuter_10(); | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileBreakNestedSimple" to """ @@ -173,6 +218,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | } | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileContinueSkip" to """ @@ -189,6 +236,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | afterLoop_6(); | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileContinueSkipSimple" to """ @@ -200,6 +249,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | } | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileContinueNested" to """ @@ -220,6 +271,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | afterOuter_10(); | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileContinueNestedSimple" to """ @@ -233,6 +286,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | } | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "whileNestedBreakContinue" to """ @@ -249,6 +304,8 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | } | return 42; |} + | + |export {} from "./implement.internal.js"; """.trimMargin(), "exprStatementHse" to """failed_0 = false, "dummy" ||(failed_0 = true, null);""", @@ -261,7 +318,7 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor |import { | type as type__9 |} from "@temperlang/core"; - |class Thing_0 extends type__9() { + |export class Thing_0 extends type__9() { | /** @param {string} blah_1 */ | constructor(blah_1) { | super (); @@ -281,7 +338,9 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | } | /** @type {string} */ | #propName_8; - |} + |}; + | + |export {} from "./implement.internal.js"; """.trimMargin(), "exportedFun" to """ @@ -298,6 +357,10 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | return_2 = optionalArg_1; | return return_2; |}; + | + |export { + | funName + |} from "./implement.internal.js"; """.trimMargin(), "funLambdaArgs" to """ @@ -308,8 +371,10 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | * @param {(arg0: string, arg1: string) => X_4} gamma_3 | * @returns {string} | */ - |function function_0(alpha_1, beta_2, gamma_3) { - |} + |export function function_0(alpha_1, beta_2, gamma_3) { + |}; + | + |export {} from "./implement.internal.js"; """.trimMargin(), "trailingRequiredArgs" to """ @@ -319,14 +384,16 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | * @param {number} gamma_3 | * @returns {number} | */ - |function function_0(alpha_1, beta_2, gamma_3) { + |export function function_0(alpha_1, beta_2, gamma_3) { | let return_4; | if (true) { | beta_2 = 77; | } | return_4 = alpha_1 + beta_2 + gamma_3; | return return_4; - |} + |}; + | + |export {} from "./implement.internal.js"; """.trimMargin(), "simpleGenerator" to """ @@ -334,10 +401,12 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor | adaptAwaiter as adaptAwaiter__2 |} from "@temperlang/core"; |/** @returns {Generator<{}>} */ - |const simpleGenerator_0 = adaptAwaiter__2(function* simpleGenerator_0(await_1) { + |export const simpleGenerator_0 = adaptAwaiter__2(function* simpleGenerator_0(await_1) { | yield null; | return empty_3; |}); + | + |export {} from "./implement.internal.js"; """.trimMargin(), ) } From 1a3ef4c697f88a0ddd1827d2b3bda0c55e63e7d1 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 27 Jul 2026 17:34:55 -0700 Subject: [PATCH 03/12] Pass test test and build test Signed-off-by: Tom --- .../kotlin/lang/temper/be/js/JsBackend.kt | 129 ------------------ .../kotlin/lang/temper/be/js/JsTranslator.kt | 21 +-- .../test/kotlin/lang/temper/cli/BuildTest.kt | 10 +- 3 files changed, 16 insertions(+), 144 deletions(-) diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt index ec0d5975..0fa419fe 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt @@ -43,7 +43,6 @@ import lang.temper.log.unknownPos import lang.temper.name.BackendId import lang.temper.name.BackendMeta import lang.temper.name.DashedIdentifier -import lang.temper.name.ExportedName import lang.temper.name.FileType import lang.temper.name.LanguageLabel import lang.temper.name.Symbol @@ -324,85 +323,9 @@ class JsBackend private constructor( } private fun prepareTesting(program: Js.Program, stdTestingRelativePath: String?) { - // If we have a relative path to std/testing, it must be because we're not using the standard name. - // So default to the optional relative path. - // TODO Once we standardize imports for funtests, always just use the global name. - val stdTestingPrefix = stdTestingRelativePath ?: run { - val std = libraryConfigurations.byLibraryName.getValue(DashedIdentifier.temperStandardLibraryIdentifier) - "${std.jsLibraryName()}/$TESTING_BASENAME" - } this.jsDependencies = this.jsDependencies .withTestDependency(JsDependency("mocha", "^10.0.0", null)) .withTestDependency(JsDependency("mocha-junit-reporter", "^2.0.2", null)) - // The overall goal is to find code that imports from the std/testing lib, move them to the test - // directory and convert the import to something like - // const test_17 = it; - // import assert from 'assert'; - // const assert_35 = assert; - // The mixed import style is apparently required. - data class DeclarationInfo(val pos: Position, val name: Js.Identifier) - program.topLevel = program.topLevel.flatMap { topLevel -> - var itName: DeclarationInfo? = null - when (topLevel) { - is Js.ImportDeclaration -> - // use the prefix to abstract over .temper.md and .md - if (topLevel.source.value.startsWith(stdTestingPrefix)) { - topLevel.specifiers = topLevel.specifiers.flatMap { imported: Js.Imported -> - when (imported) { - is Js.ImportDefaultSpecifier -> - listOf(imported) - - is Js.ImportNamespaceSpecifier -> listOf(imported) - is Js.ImportSpecifiers -> { - val updatedSpecifiers = imported.specifiers.flatMap { specifier -> - with(specifier.local.name.text) { - when { - startsWith("test_") -> listOf() - else -> listOf(specifier) - } - } - } - if (updatedSpecifiers.isEmpty()) { - emptyList() - } else { - imported.specifiers = updatedSpecifiers - listOf(imported) - } - } - } - } - // Need this to enable the smart cast since otherwise you have a var in a closure - val itInfo = itName - val itTopLevels: List = if (itInfo != null) { - listOf( - Js.VariableDeclaration( - itInfo.pos, - listOf( - Js.VariableDeclarator( - itInfo.pos, - itInfo.name, - init = Js.Identifier( - itInfo.pos, - JsIdentifierName("it"), - null, - ), - ), - ), - Js.DeclarationKind.Const, - ), - ) - } else { - emptyList() - } - itTopLevels + - if (topLevel.specifiers.isNotEmpty()) listOf(topLevel) else emptyList() - } else { - listOf(topLevel) - } - - else -> listOf(topLevel) - } - } } override val supportNetwork: SupportNetwork get() = JsSupportNetwork @@ -588,58 +511,6 @@ private fun loadTemperCorePackageJson(): JsonObject { /** convention of mocha that all tests are in the test directory */ internal val testDir = dirPath("test") -private fun findDeclaredNames( - topLevel: Js.TopLevel, - exportedNames: MutableMap>, - textFile: FilePath, -): List = when (topLevel) { - is Js.FunctionDeclaration -> listOf(topLevel.id.name) - is Js.ClassDeclaration -> listOf(topLevel.id.name) - is Js.VariableDeclaration -> { - val ids = mutableListOf() - topLevel.declarations.forEach { declaredInPattern(it.id, ids) } - ids.toList() - } - - is Js.DocumentedDeclaration -> findDeclaredNames(topLevel.decl, exportedNames, textFile) - - is Js.Statement -> emptyList() - is Js.ImportDeclaration -> - topLevel.specifiers.flatMap { imported -> - when (imported) { - is Js.ImportSpecifiers -> imported.specifiers.map { it.local.name } - is Js.ImportDefaultSpecifier -> emptyList() - is Js.ImportNamespaceSpecifier -> emptyList() - } - } - - is Js.ExportNamedDeclaration -> { - val id = when (val declaration = topLevel.declaration) { - is Js.ClassDeclaration -> declaration.id - is Js.ExceptionDeclaration -> null - is Js.FunctionDeclaration -> declaration.id - is Js.VariableDeclaration -> declaration.declarations.first().id as? Js.Identifier - null -> null - } - val sourceId = id?.sourceIdentifier - val exported = id?.name - if (exported == null) { - listOf() - } else if (sourceId is ExportedName) { - // TODO Conjure missing imports to these. - check(sourceId !in exportedNames) - exportedNames[sourceId] = textFile to exported - emptyList() - } else { - listOf(exported) - } - } - - is Js.ExportDefaultDeclaration, - is Js.ExportAllDeclaration, - -> emptyList() -} - private fun FilePath.importReadyPath(): String = this.segments.importReadyPath(isDir = this.isDir) diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt index 755438da..3b4a8073 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt @@ -226,14 +226,16 @@ internal class JsTranslator( // Also import from prod to test. Technically could be empty, but meh. val publicOutPath = t.codeLocation.outputPath val internalOutPath = publicOutPath.withExtension(JsBackend.INTERNAL_EXTENSION)!! - Js.ImportDeclaration( - t.pos, - importsFromProdToTest.map { idName -> - val id = Js.Identifier(t.pos, idName, sourceIdentifier = null) - Js.ImportSpecifier(t.pos, id, id.deepCopy()) - }.let { listOf(Js.ImportSpecifiers(t.pos, it)) }, - Js.StringLiteral(t.pos, "../${internalOutPath.segments.last()}"), - ).also { testModuleParts.explicitImports.add(it) } + if (importsFromProdToTest.isNotEmpty()) { + Js.ImportDeclaration( + t.pos, + importsFromProdToTest.map { idName -> + val id = Js.Identifier(t.pos, idName, sourceIdentifier = null) + Js.ImportSpecifier(t.pos, id, id.deepCopy()) + }.let { listOf(Js.ImportSpecifiers(t.pos, it)) }, + Js.StringLiteral(t.pos, "../${internalOutPath.segments.last()}"), + ).also { testModuleParts.explicitImports.add(it) } + } val result = t.result if (result != null) { @@ -1441,8 +1443,7 @@ internal class JsTranslator( && name is ModularName && name.comesFrom(jsNames.origin) ) { - // TODO Is sourceIdentifier really still in use? - id.sourceIdentifier = name // Store so that we can link imports to exports later. + id.sourceIdentifier = name // Provide the source name for source maps. if (name is ExportedName && name.comesFrom(jsNames.origin)) { // Only some are exported from the public module. exportedIds.add(id) diff --git a/cli/src/test/kotlin/lang/temper/cli/BuildTest.kt b/cli/src/test/kotlin/lang/temper/cli/BuildTest.kt index b9bdc2c0..2071e400 100644 --- a/cli/src/test/kotlin/lang/temper/cli/BuildTest.kt +++ b/cli/src/test/kotlin/lang/temper/cli/BuildTest.kt @@ -194,7 +194,7 @@ class BuildTest { fun jsBackend() = runBuildTest("JsBackend") { topDir -> runCase(topDir, listOf(JsBackend.Factory.backendId)) // Look just a bit at files and imports. - topDir.withTextOf("temper.out/js/lib-a/a.js") { text -> + topDir.withTextOf("temper.out/js/lib-a/a.internal.js") { text -> assertContains(text, """} from "lib-b/helper";""") assertContains(text, """} from "@temperlang/std/regex";""") } @@ -218,16 +218,16 @@ class BuildTest { @Test fun jsBackendDirs() = runBuildTest("JsBackendDirs", path = "/buildDirs") { topDir -> val result = runBuild(backends = listOf(JsBackend.Factory.backendId), workRoot = topDir).first - topDir.withTextOf("temper.out/js/apple/apple.js") { text -> + topDir.withTextOf("temper.out/js/apple/apple.internal.js") { text -> assertContains(text, "export function thrice") } - topDir.withTextOf("temper.out/js/apple/avocado.js") { text -> + topDir.withTextOf("temper.out/js/apple/avocado.internal.js") { text -> assertContains(text, "export class Person") } - topDir.withTextOf("temper.out/js/apple/avocado/artichoke.js") { text -> + topDir.withTextOf("temper.out/js/apple/avocado/artichoke.internal.js") { text -> assertContains(text, "export function repeated") } - topDir.withTextOf("temper.out/js/banana/banana.js") { text -> + topDir.withTextOf("temper.out/js/banana/banana.internal.js") { text -> assertNotContains(text, "@temperlang/std/testing") assertContains(text, "export function twice") assertNotContains(text, "nobodyWantsMe") From ce4651fa74c66d12e2d098d1e700ae064109dbad Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 27 Jul 2026 18:07:42 -0700 Subject: [PATCH 04/12] Clean more unused Signed-off-by: Tom --- .../kotlin/lang/temper/be/js/JsBackend.kt | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt index 0fa419fe..a75a1867 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt @@ -6,12 +6,10 @@ import lang.temper.be.BackendHelpTopicKey import lang.temper.be.BackendHelpTopicKeys import lang.temper.be.BackendSetup import lang.temper.be.tmpl.SupportNetwork -import lang.temper.be.tmpl.TESTING_BASENAME import lang.temper.be.tmpl.TmpL import lang.temper.be.tmpl.TmpLTranslator import lang.temper.be.tmpl.findCommonTopLevels import lang.temper.be.tmpl.injectSuperCallMethods -import lang.temper.be.tmpl.matchesStdTesting import lang.temper.common.MimeType import lang.temper.common.json.JsonObject import lang.temper.common.json.JsonString @@ -33,7 +31,6 @@ import lang.temper.log.FilePath.Companion.join import lang.temper.log.FilePathSegment import lang.temper.log.FilePathSegmentOrPseudoSegment import lang.temper.log.ParentPseudoFilePathSegment -import lang.temper.log.Position import lang.temper.log.SameDirPseudoFilePathSegment import lang.temper.log.UNIX_FILE_SEGMENT_SEPARATOR import lang.temper.log.dirPath @@ -202,7 +199,6 @@ class JsBackend private constructor( val jsLibraryNames = libraryConfigurations.byLibraryName.mapValues { it.value.jsLibraryName() } // Prep for test identification. - var stdTestingPath: FilePath? = null val testPaths = mutableSetOf() val translations: List = @@ -217,18 +213,10 @@ class JsBackend private constructor( ) translator.translate(tmpLModule) } - // Extract some info. + // Extract test paths. for (translation in translations) { - val codeLocation = translation.tmpLModule.codeLocation.codeLocation - when (translation.dependencyCategory) { - DependencyCategory.Production -> { - if (matchesStdTesting(codeLocation, libraryConfigurations)) { - stdTestingPath = translation.outPath - } - } - DependencyCategory.Test -> { - testPaths.add(translation.outPath) - } + if (translation.dependencyCategory == DependencyCategory.Test) { + testPaths.add(translation.outPath) } } @@ -237,12 +225,9 @@ class JsBackend private constructor( val updatedTranslations = translations .map translations@{ (outPath, program, tmpLModule) -> if (outPath in testPaths) { - // Functional tests still uses renamed std imports. - // TODO Remove this if we standardize funtests to same imports as elsewhere. - val stdTestingRelativePath = stdTestingPath?.let { - outPath.relativePathTo(stdTestingPath).joinToString("/") - } - prepareTesting(program, stdTestingRelativePath) + jsDependencies = jsDependencies + .withTestDependency(JsDependency("mocha", "^10.0.0", null)) + .withTestDependency(JsDependency("mocha-junit-reporter", "^2.0.2", null)) } val outFile = TranslatedFileSpecification( @@ -322,12 +307,6 @@ class JsBackend private constructor( return allOutputFiles } - private fun prepareTesting(program: Js.Program, stdTestingRelativePath: String?) { - this.jsDependencies = this.jsDependencies - .withTestDependency(JsDependency("mocha", "^10.0.0", null)) - .withTestDependency(JsDependency("mocha-junit-reporter", "^2.0.2", null)) - } - override val supportNetwork: SupportNetwork get() = JsSupportNetwork override fun wrapTokenSink(tokenSink: TokenSink): TokenSink = Companion.wrapTokenSink(tokenSink) @@ -466,6 +445,7 @@ private data class JsDependency( val temperLibraryName: DashedIdentifier?, ) private data class JsDependencies( + // We'll associate these by name later, which will collapse any redundancies in the lists. val runtimeDependencies: List, val testDependencies: List, ) { From 9642fd3004d49209aa83662b6045bae5a57a4d3d Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 28 Jul 2026 13:43:00 -0700 Subject: [PATCH 05/12] Connect js except ugly type names Signed-off-by: Tom --- .../kotlin/lang/temper/be/js/JsBackend.kt | 27 ++- .../kotlin/lang/temper/be/js/JsTranslator.kt | 186 ++++++++++++------ .../lang/temper/tests/FunctionalTestStatus.kt | 2 +- .../functions/connected/_connected.js | 20 ++ .../resources/functions/connected/_support.js | 11 ++ 5 files changed, 177 insertions(+), 69 deletions(-) create mode 100644 functional-test-suite/src/commonMain/resources/functions/connected/_connected.js create mode 100644 functional-test-suite/src/commonMain/resources/functions/connected/_support.js diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt index a75a1867..aca7fab1 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt @@ -16,6 +16,7 @@ import lang.temper.common.json.JsonString import lang.temper.common.json.JsonValueBuilder import lang.temper.common.structure.PropertySink import lang.temper.common.structure.StructureParser +import lang.temper.common.subListToEnd import lang.temper.format.TokenSink import lang.temper.fs.declareResources import lang.temper.fs.loadResource @@ -219,6 +220,14 @@ class JsBackend private constructor( testPaths.add(translation.outPath) } } + // Connected files. + val connectedFiles = rawBackendFiles.map { file -> + MetadataFileSpecification( + path = FilePath(file.key.segments.subListToEnd(1), isDir = false), + mimeType = MimeType.javascript, + content = file.value, + ) + } val allOutputFiles = if (config.makeMetaDataFile) { val dependencyNames = mutableSetOf() @@ -278,18 +287,22 @@ class JsBackend private constructor( // the interface of any top-level module. exports["."] = mainFilePath - buildList { + buildList { addAll(updatedTranslations) + addAll(connectedFiles) add(generateMainJsForFileModules(mainFilePath, exportingTranslations)) add(generatePackageJson(exports = exports)) } } else { - translations.map translations@{ (outPath, program) -> - TranslatedFileSpecification( - outPath, - MimeType.javascript, - program, - ) + buildList { + for ((outPath, program) in translations) { + TranslatedFileSpecification( + outPath, + MimeType.javascript, + program, + ).also { add(it) } + } + addAll(connectedFiles) } } diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt index 3b4a8073..e04c4955 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt @@ -13,8 +13,10 @@ import lang.temper.be.tmpl.TypedArg import lang.temper.be.tmpl.dependencyCategory import lang.temper.be.tmpl.findDeclaration import lang.temper.be.tmpl.implicitTypeTag +import lang.temper.be.tmpl.isStdLib import lang.temper.be.tmpl.libraryName import lang.temper.be.tmpl.mapGeneric +import lang.temper.be.tmpl.parameterDefaultStatementsInfo import lang.temper.be.tmpl.toTmpL import lang.temper.be.tmpl.typeOrInvalid import lang.temper.be.tmpl.withoutBubbleOrNull @@ -76,6 +78,7 @@ import lang.temper.value.TString import lang.temper.value.TSymbol import lang.temper.value.TType import lang.temper.value.TVoid +import lang.temper.value.connectedSymbol import kotlin.io.path.Path private const val DEBUG = false @@ -137,18 +140,19 @@ internal class JsTranslator( defaultGenre } - private var libraryName: DashedIdentifier? = null + private var module: TmpL.Module? = null private val importedIds = mutableListOf() private val exportedIds = mutableListOf() private val importsFromProdToTest = mutableSetOf() private val prodTopIds = mutableSetOf() + private var hasConnected = false fun translate(t: TmpL.Module): List = jsNames.forOrigin(t.codeLocation.origin) { debug { console.log("$t") } - libraryName = t.libraryName + module = t // Build imports before topLevels, or else local import names get mismatched. val ungroupedImports = mutableListOf() @@ -204,6 +208,16 @@ internal class JsTranslator( } val prodImports = filterImports(prodModuleParts.topLevels, imports) prodModuleParts.explicitImports.addAll(prodImports) + if (hasConnected) { + Js.ImportDeclaration( + t.pos, + specifiers = Js.ImportNamespaceSpecifier( + t.pos, + Js.Identifier(t.pos, connectedName, null) + ).let { listOf(it) }, + source = Js.StringLiteral(t.pos, "./_connected.js"), + ).also { prodModuleParts.explicitImports.add(it) } + } // Copy imports to test module as needed, with relative paths adjusted. val hasTests = t.moduleMetadata.dependencyCategory == DependencyCategory.Test @@ -229,11 +243,11 @@ internal class JsTranslator( if (importsFromProdToTest.isNotEmpty()) { Js.ImportDeclaration( t.pos, - importsFromProdToTest.map { idName -> + specifiers = importsFromProdToTest.map { idName -> val id = Js.Identifier(t.pos, idName, sourceIdentifier = null) Js.ImportSpecifier(t.pos, id, id.deepCopy()) }.let { listOf(Js.ImportSpecifiers(t.pos, it)) }, - Js.StringLiteral(t.pos, "../${internalOutPath.segments.last()}"), + source = Js.StringLiteral(t.pos, "../${internalOutPath.segments.last()}"), ).also { testModuleParts.explicitImports.add(it) } } @@ -1344,7 +1358,7 @@ internal class JsTranslator( } private fun translateTest(t: TmpL.Test): Js.TopLevel { - dependenciesBuilder?.addTest(libraryName, t) + dependenciesBuilder?.addTest(module!!.libraryName, t) val testName = Js.StringLiteral(t.name.pos, t.rawName) // it("test name", function expression); return Js.ExpressionStatement( @@ -1505,70 +1519,77 @@ internal class JsTranslator( val (fd, maskedThis) = jsNames.withLocalNameForThis(d.parameters.thisName?.name) { val leftPos = d.pos.leftEdge + val params = buildList { + if (generatorNameAllocated != null) { + add(Js.Param(leftPos, Js.Identifier(leftPos, generatorNameAllocated, null))) + } + d.parameters.parameters.mapNotNullTo(this) { formal -> + // do not translate `this` params + (translateId(formal.name, useThisStack = true) as? Js.Identifier)?.let { identifier -> + Js.Param(formal.pos, identifier) + } + } + d.parameters.restParameter?.let { restFormal -> + add( + Js.Param( + restFormal.pos, + Js.RestElement(restFormal.pos, translateIdStrict(restFormal.name)), + ), + ) + } + } JsFnParts( pos = d.pos, id = name.deepCopy(), - params = Js.Formals( - d.parameters.pos, - buildList { - if (generatorNameAllocated != null) { - add(Js.Param(leftPos, Js.Identifier(leftPos, generatorNameAllocated, null))) - } - d.parameters.parameters.mapNotNullTo(this) { formal -> - // do not translate `this` params - (translateId(formal.name, useThisStack = true) as? Js.Identifier)?.let { identifier -> - Js.Param(formal.pos, identifier) - } - } - d.parameters.restParameter?.let { restFormal -> - add( - Js.Param( - restFormal.pos, - Js.RestElement(restFormal.pos, translateIdStrict(restFormal.name)), - ), - ) - } - }, - ), + params = Js.Formals(d.parameters.pos, params), body = d.body?.let { block -> - // Sometimes pureVirtual methods come out like `myVirtualMethod() { return_1 = null; }`, - // which is invalid in strict mode (return_1 is never declared). - // So this finds those and makes it run the block below, - if (block.statements.size == 1) { - when (val first = block.statements.firstOrNull()) { - is TmpL.Assignment -> when (val maybeReturn = first.left.name) { - is SourceName -> if (maybeReturn.baseName.nameText == "return") { - return@let null + when (d) { + is TmpL.FunctionDeclaration + if d.metadata.any { it.key.symbol == connectedSymbol } && !module!!.isStdLib + -> translateConnectedBody(d, params) + else -> { + // Sometimes pureVirtual methods come out like `myVirtualMethod() { return_1 = null; }`, + // which is invalid in strict mode (return_1 is never declared). + // So this finds those and makes it run the block below, + if (block.statements.size == 1) { + when (val first = block.statements.firstOrNull()) { + is TmpL.Assignment -> when (val maybeReturn = first.left.name) { + is SourceName -> if (maybeReturn.baseName.nameText == "return") { + return@let null + } + + else -> {} + } + + else -> {} } - else -> {} } - else -> {} - } - } - val body = translateBlockStatement(block, prefix = buildNullDefaults(d.parameters)) - - if (d is TmpL.Constructor) { - Js.BlockStatement( - d.pos, - buildList { - add( - Js.ExpressionStatement( - d.pos, - Js.CallExpression( - d.pos, - Js.Super(d.pos), - listOf(), - ), - ), - ) - addAll( - body.body.map { it.deepCopy() }, + val body = translateBlockStatement(block, prefix = buildNullDefaults(d.parameters)) + + if (d is TmpL.Constructor) { + Js.BlockStatement( + d.pos, + buildList { + add( + Js.ExpressionStatement( + d.pos, + Js.CallExpression( + d.pos, + Js.Super(d.pos), + listOf(), + ), + ), + ) + addAll( + body.body.map { it.deepCopy() }, + ) + }, ) - }, - ) - } else { - body + } else { + body + } + } } } ?: return@withLocalNameForThis null, mayYield = d.mayYield, @@ -1643,6 +1664,47 @@ internal class JsTranslator( } } + private fun translateConnectedBody( + d: TmpL.FunctionDeclaration, + params: List, + ): Js.BlockStatement { + hasConnected = true + val pos = d.pos + val defaulting = d.parameterDefaultStatementsInfo() + return buildList { + for (statement in defaulting.defaultStatements) { + addAll(translateStatement(statement)) + } + Js.CallExpression( + pos, + callee = Js.MemberExpression( + pos, + obj = Js.Identifier(pos, connectedName, null), + property = when (val name = d.name.name) { + is ResolvedParsedName -> JsIdentifierName.escaped(name.baseName.nameText) + else -> jsNames.jsNameNotThis(name) + }.let { Js.Identifier(pos, it, d.name.name) }, + ), + arguments = buildList { + for ((tmpl, java) in d.parameters.parameters.zip(params)) { + when { + tmpl.optional -> { + val defaultedName = defaulting.parameterMapping.getValue(tmpl.name.name) + Js.Identifier(pos, jsNames.jsNameNotThis(defaultedName), tmpl.name.name) + } + else -> (java.pattern as? Js.Identifier)?.deepCopy() + }?.also { add(it) } + } + }, + ).let { call -> + when { + d.returnType.isVoid -> Js.ExpressionStatement(pos, call) + else -> Js.ReturnStatement(pos, call) + } + }.also { add(it) } + }.let { Js.BlockStatement(pos, it) } + } + private fun buildNullDefaults(parameters: TmpL.Parameters): List = buildList { for (param in parameters.parameters) { // If we're optional and default to null, we need to convert undefined to null. @@ -2494,3 +2556,5 @@ private fun buildPublicFace(pos: Position, exporteds: List, inter ), ), ) + +internal val connectedName = JsIdentifierName("_connected") diff --git a/functional-test-suite/src/commonMain/kotlin/lang/temper/tests/FunctionalTestStatus.kt b/functional-test-suite/src/commonMain/kotlin/lang/temper/tests/FunctionalTestStatus.kt index b76128c7..da65782b 100644 --- a/functional-test-suite/src/commonMain/kotlin/lang/temper/tests/FunctionalTestStatus.kt +++ b/functional-test-suite/src/commonMain/kotlin/lang/temper/tests/FunctionalTestStatus.kt @@ -25,7 +25,7 @@ val functionalTestStatus: Map> = buildMap { issue(Ft.RegexZeroAdvance, lua(166)) issue(Ft.NamesNonascii, lua(228)) 214.let { issue(Ft.TypesNetresponse, cpp(it), interp(it), lua(it)) } - 456.let { issue(Ft.FunctionsConnected, cpp(it), csharp(it), interp(it), js(it), lua(it), rust(it)) } + 456.let { issue(Ft.FunctionsConnected, cpp(it), csharp(it), interp(it), lua(it), rust(it)) } onlyFails( cpp(198), Ft.RegexMatch, diff --git a/functional-test-suite/src/commonMain/resources/functions/connected/_connected.js b/functional-test-suite/src/commonMain/resources/functions/connected/_connected.js new file mode 100644 index 00000000..60e4bd5b --- /dev/null +++ b/functional-test-suite/src/commonMain/resources/functions/connected/_connected.js @@ -0,0 +1,20 @@ +// @ts-check +import { Hidden } from "./work.internal.js"; +import { Support } from "./_support.js"; + +/** + * @param {number} i + * @param {number} j + * @param {number} bonus + */ +export const sum = (i, j, bonus) => { + return i + j + bonus; +}; + +/** + * @param {Hidden} hidden + * @param {number} j + */ +export const prod = (hidden, j) => { + return new Support().prod(hidden.i, j); +}; diff --git a/functional-test-suite/src/commonMain/resources/functions/connected/_support.js b/functional-test-suite/src/commonMain/resources/functions/connected/_support.js new file mode 100644 index 00000000..533bb954 --- /dev/null +++ b/functional-test-suite/src/commonMain/resources/functions/connected/_support.js @@ -0,0 +1,11 @@ +// @ts-check + +export class Support { + /** + * @param {number} i + * @param {number} j + */ + prod(i, j) { + return i * j; + } +} From 4a3bf4ca4ca63bd6c8a9aa294474bbbeca03b40a Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 28 Jul 2026 15:47:41 -0700 Subject: [PATCH 06/12] Pass tests Signed-off-by: Tom --- .../kotlin/lang/temper/be/js/JsNames.kt | 7 ++++- .../kotlin/lang/temper/be/js/JsTranslator.kt | 26 +++++++++++++++++++ .../kotlin/lang/temper/be/js/JsBackendTest.kt | 4 +-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsNames.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsNames.kt index d935f62f..95fb66a8 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsNames.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsNames.kt @@ -87,7 +87,7 @@ internal class JsNames { } } - fun jsNameNotThis(name: ResolvedName): JsIdentifierName { + fun jsNameNotThis(name: ResolvedName, cachePretty: Boolean = false): JsIdentifierName { if (name in monitoredCalls) { monitoredCalls[name] = monitoredCalls[name]!! + 1 } @@ -95,6 +95,11 @@ internal class JsNames { is ExportedName if name.comesFrom(origin) -> { JsIdentifierName.escaped(name.baseName.nameText) } + else if cachePretty -> { + JsIdentifierName.escaped(name.prefix()).also { + tmpLNameToJsName[name] = it + } + } in localAliases -> localAliases.getValue(name) in availableAliases -> { val localName = unusedName(toSafePattern(name)) diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt index e04c4955..ac5bdfec 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt @@ -154,6 +154,7 @@ internal class JsTranslator( } module = t + precachePrettyTypeNames(t) // Build imports before topLevels, or else local import names get mismatched. val ungroupedImports = mutableListOf() translateImports(t.imports, ungroupedImports) @@ -299,6 +300,31 @@ internal class JsTranslator( } } + /** + * TODO Once we avoid suffices more generally, maybe can remove this. + * TODO Our semi-standardized name handling (see java or py) also might can simplify some of this out. + */ + private fun precachePrettyTypeNames(t: TmpL.Module) { + // Ensure that exporteds get priority access to pretty names. + val exporteds = buildSet { + t.topLevels.forEach topLevels@{ topLevel -> + topLevel is TmpL.Declaration || return@topLevels + val name = topLevel.name.name as? ExportedName ?: return@topLevels + add(name.baseName.nameText) + } + } + // Now cache pretty type names where we don't hit exporteds. + t.topLevels.forEach topLevels@{ topLevel -> + topLevel is TmpL.TypeDeclaration || return@topLevels + val name = topLevel.name.name + // Exported names are pretty anyway, so don't bother to rename to pretty. + name is ExportedName && return@topLevels + // And don't try to write over an actual exported. + name.prefix() in exporteds && return@topLevels + jsNames.jsNameNotThis(topLevel.name.name, cachePretty = true) + } + } + private fun effectiveDependencyCategory(t: TmpL.TopLevel): DependencyCategory? { return t.dependencyCategory() ?: when (genreTranslating) { Genre.Library -> null diff --git a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt index 4e89ef80..f5c13f12 100644 --- a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt +++ b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt @@ -874,7 +874,7 @@ class JsBackendTest { | import { | type as type_0, requireInstanceOf as requireInstanceOf_0, marshalToJsonObject as marshalToJsonObject_0 | } from "@temperlang/core"; - | export class CJsonAdapter_0 extends type_0() { + | export class CJsonAdapter extends type_0() { | /** | * @param {C} x_0 | * @param {JsonProducer_0} p_0 @@ -919,7 +919,7 @@ class JsBackendTest { | } | /** @returns {JsonAdapter_0} */ | static jsonAdapter() { - | return new CJsonAdapter_0(); + | return new CJsonAdapter(); | } | /** @returns {unknown} */ | toJSON() { From 34cefd4583b3139ee9b0e67df3934be815275154 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 28 Jul 2026 15:49:08 -0700 Subject: [PATCH 07/12] Fix lint Signed-off-by: Tom --- .../kotlin/lang/temper/be/js/JsTranslator.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt index ac5bdfec..a06181c6 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt @@ -214,7 +214,7 @@ internal class JsTranslator( t.pos, specifiers = Js.ImportNamespaceSpecifier( t.pos, - Js.Identifier(t.pos, connectedName, null) + Js.Identifier(t.pos, connectedName, null), ).let { listOf(it) }, source = Js.StringLiteral(t.pos, "./_connected.js"), ).also { prodModuleParts.explicitImports.add(it) } @@ -1478,10 +1478,10 @@ internal class JsTranslator( val name = originalName.name val id = declaration.simpleId() return if ( - id != null - && dependencyMode == DependencyCategory.Production - && name is ModularName - && name.comesFrom(jsNames.origin) + id != null && + dependencyMode == DependencyCategory.Production && + name is ModularName && + name.comesFrom(jsNames.origin) ) { id.sourceIdentifier = name // Provide the source name for source maps. if (name is ExportedName && name.comesFrom(jsNames.origin)) { @@ -1571,8 +1571,8 @@ internal class JsTranslator( body = d.body?.let { block -> when (d) { is TmpL.FunctionDeclaration - if d.metadata.any { it.key.symbol == connectedSymbol } && !module!!.isStdLib - -> translateConnectedBody(d, params) + if d.metadata.any { it.key.symbol == connectedSymbol } && !module!!.isStdLib + -> translateConnectedBody(d, params) else -> { // Sometimes pureVirtual methods come out like `myVirtualMethod() { return_1 = null; }`, // which is invalid in strict mode (return_1 is never declared). From 0fa29fbb0a98601353470d8bf8a444ebf0695197 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 28 Jul 2026 16:31:55 -0700 Subject: [PATCH 08/12] Fix translator test Signed-off-by: Tom --- .../lang/temper/be/js/JsTranslatorTest.kt | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsTranslatorTest.kt b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsTranslatorTest.kt index fb14e931..81715149 100644 --- a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsTranslatorTest.kt +++ b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsTranslatorTest.kt @@ -316,28 +316,28 @@ class JsTranslatorTest : TranslatorTests(JsBackend.Factory.backendMeta, JsSuppor "unexportedClass" to """ |import { - | type as type__9 + | type as type__8 |} from "@temperlang/core"; - |export class Thing_0 extends type__9() { - | /** @param {string} blah_1 */ - | constructor(blah_1) { + |export class Thing extends type__8() { + | /** @param {string} blah_0 */ + | constructor(blah_0) { | super (); | } | /** - | * @param {string} requiredArg_3 - | * @param {number | null} [optionalArg_4] + | * @param {string} requiredArg_2 + | * @param {number | null} [optionalArg_3] | * @returns {number} | */ - | funName(requiredArg_3, optionalArg_4) { - | let return_5; + | funName(requiredArg_2, optionalArg_3) { + | let return_4; | if (true) { - | optionalArg_4 = 1; + | optionalArg_3 = 1; | } - | return_5 = optionalArg_4; - | return return_5; + | return_4 = optionalArg_3; + | return return_4; | } | /** @type {string} */ - | #propName_8; + | #propName_7; |}; | |export {} from "./implement.internal.js"; From 60a57b8036b8ca084250bcdce048da78a31ab416 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 29 Jul 2026 09:26:34 -0700 Subject: [PATCH 09/12] Use single prod mod for doc Signed-off-by: Tom --- .../kotlin/lang/temper/be/js/JsTranslator.kt | 38 +++++++++++-------- .../lang/temper/be/js/DocJsTranslatorTest.kt | 8 ++-- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt index a06181c6..7b32cdbe 100644 --- a/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt +++ b/be-js/src/commonMain/kotlin/lang/temper/be/js/JsTranslator.kt @@ -240,7 +240,10 @@ internal class JsTranslator( // Also import from prod to test. Technically could be empty, but meh. val publicOutPath = t.codeLocation.outputPath - val internalOutPath = publicOutPath.withExtension(JsBackend.INTERNAL_EXTENSION)!! + val internalOutPath = when (genreTranslating) { + Genre.Library -> publicOutPath.withExtension(JsBackend.INTERNAL_EXTENSION)!! + Genre.Documentation -> publicOutPath + } if (importsFromProdToTest.isNotEmpty()) { Js.ImportDeclaration( t.pos, @@ -281,13 +284,15 @@ internal class JsTranslator( t, DependencyCategory.Production, ).also { add(it) } - // Public face with just the actual exports. Add it even if no exports. - Translation( - publicOutPath, - buildPublicFace(t.pos, exportedIds, internalOutPath), - t, - DependencyCategory.Production, - ).also { add(it) } + if (genreTranslating == Genre.Library) { + // Public face with just the actual exports. Add it even if no exports. + Translation( + publicOutPath, + buildPublicFace(t.pos, exportedIds, internalOutPath), + t, + DependencyCategory.Production, + ).also { add(it) } + } } if (hasTests) { Translation( @@ -1014,7 +1019,11 @@ internal class JsTranslator( TmpL.TypeDeclarationKind.Enum -> translateTypeDeclaration(d, nameText) } // Export all prod top-levels from internal. - return if (dependencyMode == DependencyCategory.Production) { + val exported = when (genreTranslating) { + Genre.Library -> dependencyMode == DependencyCategory.Production + Genre.Documentation -> d.name.name is ExportedName + } + return if (exported) { val topLevelsWithExport = topLevels.toMutableList() val toExport = topLevelsWithExport[mainDeclIndex] as Js.Declaration val toExportId = toExport.simpleId() @@ -1477,12 +1486,11 @@ internal class JsTranslator( ): Js.TopLevel { val name = originalName.name val id = declaration.simpleId() - return if ( - id != null && - dependencyMode == DependencyCategory.Production && - name is ModularName && - name.comesFrom(jsNames.origin) - ) { + val exported = id != null && name is ModularName && when (genreTranslating) { + Genre.Library -> dependencyMode == DependencyCategory.Production + Genre.Documentation -> name is ExportedName + } && name.comesFrom(jsNames.origin) + return if (exported) { id.sourceIdentifier = name // Provide the source name for source maps. if (name is ExportedName && name.comesFrom(jsNames.origin)) { // Only some are exported from the public module. diff --git a/be-js/src/commonTest/kotlin/lang/temper/be/js/DocJsTranslatorTest.kt b/be-js/src/commonTest/kotlin/lang/temper/be/js/DocJsTranslatorTest.kt index fc53ce49..e6b10ef5 100644 --- a/be-js/src/commonTest/kotlin/lang/temper/be/js/DocJsTranslatorTest.kt +++ b/be-js/src/commonTest/kotlin/lang/temper/be/js/DocJsTranslatorTest.kt @@ -14,17 +14,17 @@ class DocJsTranslatorTest { @Test fun stringConcat() = assertGeneratedDocs( - """ + $$""" |export let bar: String; |;;; - |"foo ${'$'}{ bar }" + |"foo ${bar}" """.trimMargin(), - want = """ + want = $$""" |// #region __BOILERPLATE__ {{{ |/** @type {string} */ |export let bar; |// #endregion }}} - |`foo ${'$'}{ bar }`; + |`foo ${ bar }`; """.trimMargin(), ) From 68c782e826aa032ec53af90bff8cc02bd3513eac Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 29 Jul 2026 10:30:09 -0700 Subject: [PATCH 10/12] Fix repl and layout tests Signed-off-by: Tom --- .../lang/temper/cli/js/JsRunFileLayoutTest.kt | 18 ++++++++++++++++++ .../kotlin/lang/temper/cli/repl/ReplTest.kt | 10 +++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/cli/src/test/kotlin/lang/temper/cli/js/JsRunFileLayoutTest.kt b/cli/src/test/kotlin/lang/temper/cli/js/JsRunFileLayoutTest.kt index 816afaec..f85a009a 100644 --- a/cli/src/test/kotlin/lang/temper/cli/js/JsRunFileLayoutTest.kt +++ b/cli/src/test/kotlin/lang/temper/cli/js/JsRunFileLayoutTest.kt @@ -86,18 +86,22 @@ class JsRunFileLayoutTest { | ┣━index.js | ┣━library-a/ | ┃ ┣━index.js + | ┃ ┣━library_a.internal.js | ┃ ┣━library_a.js | ┃ ┗━package.json | ┣━library-b/ | ┃ ┣━index.js + | ┃ ┣━library_b.internal.js | ┃ ┣━library_b.js | ┃ ┗━package.json | ┣━library-c/ | ┃ ┣━index.js + | ┃ ┣━library_c.internal.js | ┃ ┣━library_c.js | ┃ ┗━package.json | ┣━library-d/ | ┃ ┣━index.js + | ┃ ┣━library_d.internal.js | ┃ ┣━library_d.js | ┃ ┗━package.json | ┣━node_modules/ @@ -127,26 +131,35 @@ class JsRunFileLayoutTest { | ┃ ┃ ┃ ┗━tsconfig.json | ┃ ┃ ┗━std/ | ┃ ┃ ┣━index.js + | ┃ ┃ ┣━json.internal.js | ┃ ┃ ┣━json.js + | ┃ ┃ ┣━net.internal.js | ┃ ┃ ┣━net.js | ┃ ┃ ┣━package.json + | ┃ ┃ ┣━regex.internal.js | ┃ ┃ ┣━regex.js + | ┃ ┃ ┣━temporal.internal.js | ┃ ┃ ┣━temporal.js + | ┃ ┃ ┣━testing.internal.js | ┃ ┃ ┗━testing.js | ┃ ┣━library-a/ | ┃ ┃ ┣━index.js + | ┃ ┃ ┣━library_a.internal.js | ┃ ┃ ┣━library_a.js | ┃ ┃ ┗━package.json | ┃ ┣━library-b/ | ┃ ┃ ┣━index.js + | ┃ ┃ ┣━library_b.internal.js | ┃ ┃ ┣━library_b.js | ┃ ┃ ┗━package.json | ┃ ┣━library-c/ | ┃ ┃ ┣━index.js + | ┃ ┃ ┣━library_c.internal.js | ┃ ┃ ┣━library_c.js | ┃ ┃ ┗━package.json | ┃ ┗━library-d/ | ┃ ┣━index.js + | ┃ ┣━library_d.internal.js | ┃ ┣━library_d.js | ┃ ┗━package.json """.trimMargin() @@ -165,11 +178,16 @@ class JsRunFileLayoutTest { | ┣━package.json | ┣━std/ | ┃ ┣━index.js + | ┃ ┣━json.internal.js | ┃ ┣━json.js + | ┃ ┣━net.internal.js | ┃ ┣━net.js | ┃ ┣━package.json + | ┃ ┣━regex.internal.js | ┃ ┣━regex.js + | ┃ ┣━temporal.internal.js | ┃ ┣━temporal.js + | ┃ ┣━testing.internal.js | ┃ ┗━testing.js | ┗━temper-core/ | ┣━async.js diff --git a/cli/src/test/kotlin/lang/temper/cli/repl/ReplTest.kt b/cli/src/test/kotlin/lang/temper/cli/repl/ReplTest.kt index 91caeb9c..2aec775b 100644 --- a/cli/src/test/kotlin/lang/temper/cli/repl/ReplTest.kt +++ b/cli/src/test/kotlin/lang/temper/cli/repl/ReplTest.kt @@ -466,12 +466,16 @@ class ReplTest { """ |Translated js for interactive#0 | interactive/ - | i0000.js: text/javascript + | i0000.internal.js: text/javascript | /** @type {number} */ - | const return_0 = 2; + | export const return_0 = 2; | export default return_0; + | i0000.js: text/javascript + | export {} from "./i0000.internal.js"; | i0000.js.map: application/json - | { "version": 3, "file": "js/interactive/⋯A,MAAAA,QAAA,IAAK,AAAL;AAAK,eAAAA,QAAA" } + | { "version": 3, "file": "js/interactive/⋯ [], "mappings": "AAAK,cAAA,AAAL,sBAAK" } + | i0000.internal.js.map: application/json + | { "version": 3, "file": "js/interactive/⋯A,aAAAA,QAAA,IAAK,AAAL;AAAK,eAAAA,QAAA" } |interactive#1: void | """.trimMargin(), From d338a5d4ad8792ae356ff765ef071169e64ed881 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 29 Jul 2026 12:02:35 -0700 Subject: [PATCH 11/12] Sort repl translations Signed-off-by: Tom --- cli/src/main/kotlin/lang/temper/cli/repl/ReplTranslateFn.kt | 2 +- cli/src/test/kotlin/lang/temper/cli/repl/ReplTest.kt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cli/src/main/kotlin/lang/temper/cli/repl/ReplTranslateFn.kt b/cli/src/main/kotlin/lang/temper/cli/repl/ReplTranslateFn.kt index 26893057..febb6fe4 100644 --- a/cli/src/main/kotlin/lang/temper/cli/repl/ReplTranslateFn.kt +++ b/cli/src/main/kotlin/lang/temper/cli/repl/ReplTranslateFn.kt @@ -167,7 +167,7 @@ internal class ReplTranslateFn( fun dump(f: OutFile): Unit = when (f) { is OutDir -> { console.groupIf(f is OutSubDir, "${f.name}/") { - f.files.forEach { dump(it) } + f.files.sortedBy { it.name }.forEach { dump(it) } } } is OutRegularFile -> dumpBinary( diff --git a/cli/src/test/kotlin/lang/temper/cli/repl/ReplTest.kt b/cli/src/test/kotlin/lang/temper/cli/repl/ReplTest.kt index 2aec775b..33e5fe8f 100644 --- a/cli/src/test/kotlin/lang/temper/cli/repl/ReplTest.kt +++ b/cli/src/test/kotlin/lang/temper/cli/repl/ReplTest.kt @@ -470,12 +470,12 @@ class ReplTest { | /** @type {number} */ | export const return_0 = 2; | export default return_0; + | i0000.internal.js.map: application/json + | { "version": 3, "file": "js/interactive/⋯A,aAAAA,QAAA,IAAK,AAAL;AAAK,eAAAA,QAAA" } | i0000.js: text/javascript | export {} from "./i0000.internal.js"; | i0000.js.map: application/json | { "version": 3, "file": "js/interactive/⋯ [], "mappings": "AAAK,cAAA,AAAL,sBAAK" } - | i0000.internal.js.map: application/json - | { "version": 3, "file": "js/interactive/⋯A,aAAAA,QAAA,IAAK,AAAL;AAAK,eAAAA,QAAA" } |interactive#1: void | """.trimMargin(), @@ -503,11 +503,13 @@ class ReplTest { | java/ | interactive/ | i0000/ + | .* | I0000Main[.]java: text/x-java-source | package interactive[.]i0000; | import temper[.]core[.]Core; | """.trimMargin(), + RegexOption.DOT_MATCHES_ALL, ), ) } From 3fc4f0a92e7dd069e0cc65f0cf65e53e021a7644 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 29 Jul 2026 12:29:23 -0700 Subject: [PATCH 12/12] Update test matrix Signed-off-by: Tom --- functional-test-matrix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional-test-matrix.md b/functional-test-matrix.md index 8b3e7ffd..26536396 100644 --- a/functional-test-matrix.md +++ b/functional-test-matrix.md @@ -26,7 +26,7 @@ | [ControlFlowLoopReenterable][] | ✅ | ❌[198][] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [ControlFlowLoops][] | ✅ | ❌[198][] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [FunctionsAsValues][] | ✅ | ❌[198][] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| [FunctionsConnected][] | ❌[456][] | ❌[198][] | ❌[456][] | ❌[456][] | ✅ | ✅ | ❌[456][] | ❌[456][] | ✅ | ✅ | ❌[456][] | +| [FunctionsConnected][] | ❌[456][] | ❌[198][] | ❌[456][] | ❌[456][] | ✅ | ✅ | ✅ | ❌[456][] | ✅ | ✅ | ❌[456][] | | [FunctionsConstructorCallbacks][] | ✅ | ❌[198][] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [FunctionsDefaulting][] | ✅ | ❌[198][] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | [FunctionsLocals][] | ✅ | ❌[198][] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |