From 312e7157907dc416a19b452487ffb14f358c16d1 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 17 Feb 2026 14:51:53 +0900 Subject: [PATCH] PlayBridgeJS: Surface BridgeJS diagnostics inside editors --- .../PlayBridgeJS/Sources/JavaScript/app.js | 31 ++- .../PlayBridgeJS/Sources/JavaScript/editor.js | 67 +++++- .../PlayBridgeJS/Generated/BridgeJS.swift | 226 ++++++++++++------ .../Generated/JavaScript/BridgeJS.json | 194 ++++++++++----- .../Sources/PlayBridgeJS/main.swift | 63 +++-- .../BridgeJS/Sources/BridgeJSCore/Misc.swift | 28 ++- .../BridgeJSCore/SwiftToSkeleton.swift | 6 +- .../Sources/BridgeJSTool/BridgeJSTool.swift | 8 +- 8 files changed, 452 insertions(+), 171 deletions(-) diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index 7da78e038..5580c802d 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -268,19 +268,38 @@ export class BridgeJSPlayground { try { this.hideError(); + this.editorSystem.clearDiagnostics(); const inputs = this.editorSystem.getInputs(); const swiftCode = inputs.swift; const dtsCode = inputs.dts; // Process the code and get PlayBridgeJSOutput - const result = this.playBridgeJS.update(swiftCode, dtsCode); - - // Update outputs using the PlayBridgeJSOutput object - this.editorSystem.updateOutputs(result); - - console.log('Code generated successfully'); + const result = this.playBridgeJS.updateDetailed(swiftCode, dtsCode); + + const diagnostics = result.diagnostics; + if (diagnostics && diagnostics.length > 0) { + const mapped = diagnostics.map(d => ({ + file: d.file, + startLineNumber: d.startLine, + startColumn: d.startColumn, + endLineNumber: d.endLine, + endColumn: d.endColumn, + message: d.message + })); + this.editorSystem.showDiagnostics(mapped); + return; + } + const output = result.output; + if (output) { + // Update outputs using the PlayBridgeJSOutput object + this.editorSystem.updateOutputs(output); + this.hideError(); + console.log('Code generated successfully'); + } else { + this.showError('No output produced.'); + } } catch (error) { console.error('Error generating code:', error); this.showError('Error generating code: ' + error.message); diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/editor.js b/Examples/PlayBridgeJS/Sources/JavaScript/editor.js index dc3d362eb..c894598a4 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/editor.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/editor.js @@ -43,7 +43,7 @@ export class EditorSystem { language: 'swift', placeholder: '// Import Swift Macros will appear here...', readOnly: true, - modelUri: 'BridgeJS.Macros.swift' + modelUri: 'Playground.Macros.swift' }, { key: 'swift-glue', @@ -206,10 +206,10 @@ export class EditorSystem { updateOutputs(result) { const outputMap = { - 'swift-glue': () => result.swiftGlue(), - 'swift-import-macros': () => result.importSwiftMacroDecls(), - 'js-generated': () => result.outputJs(), - 'dts-generated': () => result.outputDts() + 'swift-glue': () => result.swiftGlue, + 'swift-import-macros': () => result.importSwiftMacroDecls, + 'js-generated': () => result.outputJs, + 'dts-generated': () => result.outputDts }; Object.entries(outputMap).forEach(([key, getContent]) => { @@ -230,6 +230,63 @@ export class EditorSystem { }); } + clearDiagnostics() { + // Remove all diagnostics owned by the playground. + this.editors.forEach(editor => { + const model = editor.getModel(); + if (!model || typeof monaco === 'undefined') return; + monaco.editor.setModelMarkers(model, 'bridgejs', []); + }); + } + + /** + * @param {{file: string, startLineNumber: number, startColumn: number, endLineNumber?: number, endColumn?: number, message: string}[]} diagnostics + */ + showDiagnostics(diagnostics) { + if (typeof monaco === 'undefined') return; + + // Group diagnostics per model so we can set markers in batches. + const markersByModel = new Map(); + + diagnostics.forEach(diag => { + const model = this.findModelForFile(diag.file); + if (!model) return; + + const markers = markersByModel.get(model) ?? []; + const lineLength = model.getLineMaxColumn(diag.startLineNumber); + const endLine = diag.endLineNumber ?? diag.startLineNumber; + const endColumn = Math.min(lineLength, diag.endColumn ?? diag.startColumn + 1); + + markers.push({ + severity: monaco.MarkerSeverity.Error, + message: diag.message, + startLineNumber: diag.startLineNumber, + startColumn: diag.startColumn, + endLineNumber: endLine, + endColumn + }); + + markersByModel.set(model, markers); + }); + + markersByModel.forEach((markers, model) => { + monaco.editor.setModelMarkers(model, 'bridgejs', markers); + }); + } + + findModelForFile(fileName) { + const normalized = fileName.startsWith('/') ? fileName.slice(1) : fileName; + for (const editor of this.editors.values()) { + const model = editor.getModel(); + if (!model) continue; + const uriPath = model.uri.path.startsWith('/') ? model.uri.path.slice(1) : model.uri.path; + if (uriPath === normalized || uriPath.endsWith('/' + normalized)) { + return model; + } + } + return null; + } + // Utility methods getConfigByKey(key) { return [...this.config.input, ...this.config.output].find(c => c.key === key); diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift index f97c181d9..6e146dbc3 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift @@ -7,6 +7,153 @@ @_spi(BridgeJS) import JavaScriptKit +extension PlayBridgeJSOutput: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> PlayBridgeJSOutput { + let swiftGlue = String.bridgeJSStackPop() + let importSwiftMacroDecls = String.bridgeJSStackPop() + let outputDts = String.bridgeJSStackPop() + let outputJs = String.bridgeJSStackPop() + return PlayBridgeJSOutput(outputJs: outputJs, outputDts: outputDts, importSwiftMacroDecls: importSwiftMacroDecls, swiftGlue: swiftGlue) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.outputJs.bridgeJSStackPush() + self.outputDts.bridgeJSStackPush() + self.importSwiftMacroDecls.bridgeJSStackPush() + self.swiftGlue.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + let __bjs_cleanupId = _bjs_struct_lower_PlayBridgeJSOutput(jsObject.bridgeJSLowerParameter()) + defer { + _swift_js_struct_cleanup(__bjs_cleanupId) + } + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_PlayBridgeJSOutput())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_PlayBridgeJSOutput") +fileprivate func _bjs_struct_lower_PlayBridgeJSOutput(_ objectId: Int32) -> Int32 +#else +fileprivate func _bjs_struct_lower_PlayBridgeJSOutput(_ objectId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_PlayBridgeJSOutput") +fileprivate func _bjs_struct_lift_PlayBridgeJSOutput() -> Int32 +#else +fileprivate func _bjs_struct_lift_PlayBridgeJSOutput() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +extension PlayBridgeJSDiagnostic: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> PlayBridgeJSDiagnostic { + let endColumn = Int.bridgeJSStackPop() + let endLine = Int.bridgeJSStackPop() + let startColumn = Int.bridgeJSStackPop() + let startLine = Int.bridgeJSStackPop() + let message = String.bridgeJSStackPop() + let file = String.bridgeJSStackPop() + return PlayBridgeJSDiagnostic(file: file, message: message, startLine: startLine, startColumn: startColumn, endLine: endLine, endColumn: endColumn) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.file.bridgeJSStackPush() + self.message.bridgeJSStackPush() + self.startLine.bridgeJSStackPush() + self.startColumn.bridgeJSStackPush() + self.endLine.bridgeJSStackPush() + self.endColumn.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + let __bjs_cleanupId = _bjs_struct_lower_PlayBridgeJSDiagnostic(jsObject.bridgeJSLowerParameter()) + defer { + _swift_js_struct_cleanup(__bjs_cleanupId) + } + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_PlayBridgeJSDiagnostic())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_PlayBridgeJSDiagnostic") +fileprivate func _bjs_struct_lower_PlayBridgeJSDiagnostic(_ objectId: Int32) -> Int32 +#else +fileprivate func _bjs_struct_lower_PlayBridgeJSDiagnostic(_ objectId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_PlayBridgeJSDiagnostic") +fileprivate func _bjs_struct_lift_PlayBridgeJSDiagnostic() -> Int32 +#else +fileprivate func _bjs_struct_lift_PlayBridgeJSDiagnostic() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +extension PlayBridgeJSResult: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> PlayBridgeJSResult { + let diagnostics = [PlayBridgeJSDiagnostic].bridgeJSStackPop() + let output = Optional.bridgeJSStackPop() + return PlayBridgeJSResult(output: output, diagnostics: diagnostics) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.output.bridgeJSStackPush() + self.diagnostics.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + let __bjs_cleanupId = _bjs_struct_lower_PlayBridgeJSResult(jsObject.bridgeJSLowerParameter()) + defer { + _swift_js_struct_cleanup(__bjs_cleanupId) + } + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_PlayBridgeJSResult())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_PlayBridgeJSResult") +fileprivate func _bjs_struct_lower_PlayBridgeJSResult(_ objectId: Int32) -> Int32 +#else +fileprivate func _bjs_struct_lower_PlayBridgeJSResult(_ objectId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_PlayBridgeJSResult") +fileprivate func _bjs_struct_lift_PlayBridgeJSResult() -> Int32 +#else +fileprivate func _bjs_struct_lift_PlayBridgeJSResult() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + @_expose(wasm, "bjs_PlayBridgeJS_init") @_cdecl("bjs_PlayBridgeJS_init") public func _bjs_PlayBridgeJS_init() -> UnsafeMutableRawPointer { @@ -18,12 +165,12 @@ public func _bjs_PlayBridgeJS_init() -> UnsafeMutableRawPointer { #endif } -@_expose(wasm, "bjs_PlayBridgeJS_update") -@_cdecl("bjs_PlayBridgeJS_update") -public func _bjs_PlayBridgeJS_update(_ _self: UnsafeMutableRawPointer, _ swiftSourceBytes: Int32, _ swiftSourceLength: Int32, _ dtsSourceBytes: Int32, _ dtsSourceLength: Int32) -> UnsafeMutableRawPointer { +@_expose(wasm, "bjs_PlayBridgeJS_updateDetailed") +@_cdecl("bjs_PlayBridgeJS_updateDetailed") +public func _bjs_PlayBridgeJS_updateDetailed(_ _self: UnsafeMutableRawPointer, _ swiftSourceBytes: Int32, _ swiftSourceLength: Int32, _ dtsSourceBytes: Int32, _ dtsSourceLength: Int32) -> Void { #if arch(wasm32) do { - let ret = try PlayBridgeJS.bridgeJSLiftParameter(_self).update(swiftSource: String.bridgeJSLiftParameter(swiftSourceBytes, swiftSourceLength), dtsSource: String.bridgeJSLiftParameter(dtsSourceBytes, dtsSourceLength)) + let ret = try PlayBridgeJS.bridgeJSLiftParameter(_self).updateDetailed(swiftSource: String.bridgeJSLiftParameter(swiftSourceBytes, swiftSourceLength), dtsSource: String.bridgeJSLiftParameter(dtsSourceBytes, dtsSourceLength)) return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { @@ -36,7 +183,7 @@ public func _bjs_PlayBridgeJS_update(_ _self: UnsafeMutableRawPointer, _ swiftSo _swift_js_throw(Int32(bitPattern: $0.id)) } } - return UnsafeMutableRawPointer(bitPattern: -1).unsafelyUnwrapped + return } #else fatalError("Only available on WebAssembly") @@ -68,75 +215,6 @@ fileprivate func _bjs_PlayBridgeJS_wrap(_ pointer: UnsafeMutableRawPointer) -> I } #endif -@_expose(wasm, "bjs_PlayBridgeJSOutput_outputJs") -@_cdecl("bjs_PlayBridgeJSOutput_outputJs") -public func _bjs_PlayBridgeJSOutput_outputJs(_ _self: UnsafeMutableRawPointer) -> Void { - #if arch(wasm32) - let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).outputJs() - return ret.bridgeJSLowerReturn() - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_PlayBridgeJSOutput_outputDts") -@_cdecl("bjs_PlayBridgeJSOutput_outputDts") -public func _bjs_PlayBridgeJSOutput_outputDts(_ _self: UnsafeMutableRawPointer) -> Void { - #if arch(wasm32) - let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).outputDts() - return ret.bridgeJSLowerReturn() - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_PlayBridgeJSOutput_importSwiftMacroDecls") -@_cdecl("bjs_PlayBridgeJSOutput_importSwiftMacroDecls") -public func _bjs_PlayBridgeJSOutput_importSwiftMacroDecls(_ _self: UnsafeMutableRawPointer) -> Void { - #if arch(wasm32) - let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).importSwiftMacroDecls() - return ret.bridgeJSLowerReturn() - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_PlayBridgeJSOutput_swiftGlue") -@_cdecl("bjs_PlayBridgeJSOutput_swiftGlue") -public func _bjs_PlayBridgeJSOutput_swiftGlue(_ _self: UnsafeMutableRawPointer) -> Void { - #if arch(wasm32) - let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).swiftGlue() - return ret.bridgeJSLowerReturn() - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_PlayBridgeJSOutput_deinit") -@_cdecl("bjs_PlayBridgeJSOutput_deinit") -public func _bjs_PlayBridgeJSOutput_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { - #if arch(wasm32) - Unmanaged.fromOpaque(pointer).release() - #else - fatalError("Only available on WebAssembly") - #endif -} - -extension PlayBridgeJSOutput: ConvertibleToJSValue, _BridgedSwiftHeapObject { - var jsValue: JSValue { - return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJSOutput_wrap(Unmanaged.passRetained(self).toOpaque())))) - } -} - -#if arch(wasm32) -@_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJSOutput_wrap") -fileprivate func _bjs_PlayBridgeJSOutput_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 -#else -fileprivate func _bjs_PlayBridgeJSOutput_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { - fatalError("Only available on WebAssembly") -} -#endif - #if arch(wasm32) @_extern(wasm, module: "PlayBridgeJS", name: "bjs_createTS2Swift") fileprivate func bjs_createTS2Swift() -> Int32 diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json index 743925c5f..3046e1f01 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json @@ -15,13 +15,13 @@ }, "methods" : [ { - "abiName" : "bjs_PlayBridgeJS_update", + "abiName" : "bjs_PlayBridgeJS_updateDetailed", "effects" : { "isAsync" : false, "isStatic" : false, "isThrows" : true }, - "name" : "update", + "name" : "updateDetailed", "parameters" : [ { "label" : "swiftSource", @@ -43,8 +43,8 @@ } ], "returnType" : { - "swiftHeapObject" : { - "_0" : "PlayBridgeJSOutput" + "swiftStruct" : { + "_0" : "PlayBridgeJSResult" } } } @@ -54,97 +54,175 @@ ], "swiftCallName" : "PlayBridgeJS" - }, + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + + ], + "protocols" : [ + + ], + "structs" : [ { "methods" : [ + + ], + "name" : "PlayBridgeJSOutput", + "properties" : [ { - "abiName" : "bjs_PlayBridgeJSOutput_outputJs", - "effects" : { - "isAsync" : false, - "isStatic" : false, - "isThrows" : false - }, + "isReadonly" : true, + "isStatic" : false, "name" : "outputJs", - "parameters" : [ - - ], - "returnType" : { + "type" : { "string" : { } } }, { - "abiName" : "bjs_PlayBridgeJSOutput_outputDts", - "effects" : { - "isAsync" : false, - "isStatic" : false, - "isThrows" : false - }, + "isReadonly" : true, + "isStatic" : false, "name" : "outputDts", - "parameters" : [ - - ], - "returnType" : { + "type" : { "string" : { } } }, { - "abiName" : "bjs_PlayBridgeJSOutput_importSwiftMacroDecls", - "effects" : { - "isAsync" : false, - "isStatic" : false, - "isThrows" : false - }, + "isReadonly" : true, + "isStatic" : false, "name" : "importSwiftMacroDecls", - "parameters" : [ - - ], - "returnType" : { + "type" : { "string" : { } } }, { - "abiName" : "bjs_PlayBridgeJSOutput_swiftGlue", - "effects" : { - "isAsync" : false, - "isStatic" : false, - "isThrows" : false - }, + "isReadonly" : true, + "isStatic" : false, "name" : "swiftGlue", - "parameters" : [ - - ], - "returnType" : { + "type" : { "string" : { } } } ], - "name" : "PlayBridgeJSOutput", - "properties" : [ + "swiftCallName" : "PlayBridgeJSOutput" + }, + { + "methods" : [ ], - "swiftCallName" : "PlayBridgeJSOutput" - } - ], - "enums" : [ + "name" : "PlayBridgeJSDiagnostic", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "file", + "type" : { + "string" : { - ], - "exposeToGlobal" : false, - "functions" : [ + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "message", + "type" : { + "string" : { - ], - "protocols" : [ + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "startLine", + "type" : { + "int" : { - ], - "structs" : [ + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "startColumn", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "endLine", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "endColumn", + "type" : { + "int" : { + + } + } + } + ], + "swiftCallName" : "PlayBridgeJSDiagnostic" + }, + { + "methods" : [ + ], + "name" : "PlayBridgeJSResult", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "output", + "type" : { + "nullable" : { + "_0" : { + "swiftStruct" : { + "_0" : "PlayBridgeJSOutput" + } + }, + "_1" : "null" + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "diagnostics", + "type" : { + "array" : { + "_0" : { + "swiftStruct" : { + "_0" : "PlayBridgeJSDiagnostic" + } + } + } + } + } + ], + "swiftCallName" : "PlayBridgeJSResult" + } ] }, "imported" : { diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift index 80572962a..ec9eda774 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift @@ -1,14 +1,40 @@ import JavaScriptEventLoop import JavaScriptKit import SwiftParser +import SwiftSyntax import class Foundation.JSONDecoder @JS class PlayBridgeJS { @JS init() {} - @JS func update(swiftSource: String, dtsSource: String) throws(JSException) -> PlayBridgeJSOutput { + /// Structured entry point used by the playground so JS doesn't need to parse diagnostics. + @JS func updateDetailed(swiftSource: String, dtsSource: String) throws(JSException) -> PlayBridgeJSResult { do { - return try _update(swiftSource: swiftSource, dtsSource: dtsSource) + let output = try _update(swiftSource: swiftSource, dtsSource: dtsSource) + return PlayBridgeJSResult(output: output, diagnostics: []) + } catch let error as BridgeJSCoreDiagnosticError { + let diagnostics = error.diagnostics.map { diag -> PlayBridgeJSDiagnostic in + let converter = SourceLocationConverter(fileName: diag.file, tree: diag.diagnostic.node.root) + let start = converter.location(for: diag.diagnostic.node.positionAfterSkippingLeadingTrivia) + let end = converter.location(for: diag.diagnostic.node.endPositionBeforeTrailingTrivia) + + let startLine = start.line + let startColumn = start.column + let endLine = end.line + let endColumn = max(startColumn + 1, end.column) + + return PlayBridgeJSDiagnostic( + file: diag.file, + message: diag.diagnostic.message, + startLine: startLine, + startColumn: startColumn, + endLine: endLine, + endColumn: endColumn + ) + } + return PlayBridgeJSResult(output: nil, diagnostics: diagnostics) + } catch let error as BridgeJSCoreError { + throw JSException(message: error.description) } catch let error as JSException { throw error } catch { @@ -49,23 +75,26 @@ import class Foundation.JSONDecoder swiftGlue: (importResult ?? "") + "\n\n" + (exportResult ?? "") ) } + } -@JS class PlayBridgeJSOutput { - let _outputJs: String - let _outputDts: String - let _importSwiftMacroDecls: String - let _swiftGlue: String +@JS struct PlayBridgeJSOutput { + let outputJs: String + let outputDts: String + let importSwiftMacroDecls: String + let swiftGlue: String +} - init(outputJs: String, outputDts: String, importSwiftMacroDecls: String, swiftGlue: String) { - self._outputJs = outputJs - self._outputDts = outputDts - self._importSwiftMacroDecls = importSwiftMacroDecls - self._swiftGlue = swiftGlue - } +@JS struct PlayBridgeJSDiagnostic { + let file: String + let message: String + let startLine: Int + let startColumn: Int + let endLine: Int + let endColumn: Int +} - @JS func outputJs() -> String { self._outputJs } - @JS func outputDts() -> String { self._outputDts } - @JS func importSwiftMacroDecls() -> String { self._importSwiftMacroDecls } - @JS func swiftGlue() -> String { self._swiftGlue } +@JS struct PlayBridgeJSResult { + let output: PlayBridgeJSOutput? + let diagnostics: [PlayBridgeJSDiagnostic] } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift index 9db11b14d..06fb422a9 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift @@ -136,12 +136,12 @@ private enum JSON { import SwiftSyntax import class Foundation.ProcessInfo -struct DiagnosticError: Error { - let node: Syntax - let message: String - let hint: String? +public struct DiagnosticError: Error { + public let node: Syntax + public let message: String + public let hint: String? - init(node: some SyntaxProtocol, message: String, hint: String? = nil) { + public init(node: some SyntaxProtocol, message: String, hint: String? = nil) { self.node = Syntax(node) self.message = message self.hint = hint @@ -153,7 +153,7 @@ struct DiagnosticError: Error { /// - fileName: The name of the file to display in the output. /// - colorize: Whether to colorize the output with ANSI escape sequences. /// - Returns: The formatted diagnostic error string. - func formattedDescription(fileName: String, colorize: Bool = Self.shouldColorize) -> String { + public func formattedDescription(fileName: String, colorize: Bool = Self.shouldColorize) -> String { let displayFileName = fileName == "-" ? "" : fileName let converter = SourceLocationConverter(fileName: displayFileName, tree: node.root) let startLocation = converter.location(for: node.positionAfterSkippingLeadingTrivia) @@ -273,7 +273,7 @@ struct DiagnosticError: Error { return "\(gutter) | \(text)" } - private static var shouldColorize: Bool { + public static var shouldColorize: Bool { let env = ProcessInfo.processInfo.environment let termIsDumb = env["TERM"] == "dumb" return env["NO_COLOR"] == nil && !termIsDumb @@ -286,6 +286,20 @@ struct DiagnosticError: Error { return String.Index(utf8Index, within: line)! } } +/// Carries the diagnostics produced during SwiftToSkeleton. +public struct BridgeJSCoreDiagnosticError: Swift.Error, CustomStringConvertible { + public let diagnostics: [(file: String, diagnostic: DiagnosticError)] + + public init(diagnostics: [(file: String, diagnostic: DiagnosticError)]) { + self.diagnostics = diagnostics + } + + public var description: String { + diagnostics + .map { (file, diag) in diag.formattedDescription(fileName: file, colorize: false) } + .joined(separator: "\n") + } +} private enum ANSI { static let reset = "\u{001B}[0;0m" diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index 1d775fbc6..0cf5a567f 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -78,10 +78,10 @@ public final class SwiftToSkeleton { } if !perSourceErrors.isEmpty { - let allErrors = perSourceErrors.flatMap { inputFilePath, errors in - errors.map { $0.formattedDescription(fileName: inputFilePath) } + let diagnostics = perSourceErrors.flatMap { inputFilePath, errors in + errors.map { (file: inputFilePath, diagnostic: $0) } } - throw BridgeJSCoreError(allErrors.joined(separator: "\n")) + throw BridgeJSCoreDiagnosticError(diagnostics: diagnostics) } let importedSkeleton: ImportedModuleSkeleton? = { let module = ImportedModuleSkeleton(children: importedFiles) diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift index 9410b2909..a71aaee44 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift @@ -61,7 +61,13 @@ import BridgeJSUtilities try run() } } catch { - printStderr("error: \(error)") + if let diagError = error as? BridgeJSCoreDiagnosticError { + diagError.diagnostics.forEach { file, diagnostic in + printStderr(diagnostic.formattedDescription(fileName: file)) + } + } else { + printStderr("error: \(error)") + } exit(1) } }