Repository navigation
Extract CmuxFoundation package (modular refactor, wave 1) #5055
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a72ee0e
20b2673
81f807f
c22f751
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // swift-tools-version: 6.0 | ||
|
|
||
| import PackageDescription | ||
|
|
||
| let package = Package( | ||
| name: "CmuxFoundation", | ||
| platforms: [ | ||
| .macOS(.v14), | ||
| ], | ||
| products: [ | ||
| .library( | ||
| name: "CmuxFoundation", | ||
| targets: ["CmuxFoundation"] | ||
| ), | ||
| ], | ||
| targets: [ | ||
| .target( | ||
| name: "CmuxFoundation", | ||
| swiftSettings: [ | ||
| .swiftLanguageMode(.v6), | ||
| .enableUpcomingFeature("ExistentialAny"), | ||
| .enableUpcomingFeature("InternalImportsByDefault"), | ||
| ] | ||
| ), | ||
| .testTarget( | ||
| name: "CmuxFoundationTests", | ||
| dependencies: ["CmuxFoundation"] | ||
| ), | ||
| ] | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # CmuxFoundation | ||
|
|
||
| Shared low-level primitives for cmux with no internal package dependencies. This is the | ||
| bottom of the package dependency graph: encoding/text helpers, value types, and other | ||
| cross-cutting utilities that several domains need, with nothing in here depending on AppKit, | ||
| SwiftUI, or another cmux package. | ||
|
|
||
| It exists as the leaf every other package and the app target can depend on without creating | ||
| a cycle. Keep it dependency-free. | ||
|
|
||
| Foundation helpers are exposed as extensions on existing types rather than free functions, | ||
| so call sites read naturally (`value.javaScriptStringLiteral`, not `f(value)`). | ||
|
|
||
| ## Contents | ||
|
|
||
| - `String.javaScriptStringLiteral` — the string encoded as a quoted JavaScript string literal. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```swift | ||
| import CmuxFoundation | ||
|
|
||
| let literal = userText?.javaScriptStringLiteral ?? "null" | ||
| webView.evaluateJavaScript("setValue(\(literal))") | ||
| ``` | ||
|
|
||
| ## Testing | ||
|
|
||
| Everything here is a pure value transform, so tests need no app, no AppKit, and no filesystem: | ||
|
|
||
| ```swift | ||
| import Testing | ||
| import CmuxFoundation | ||
|
|
||
| @Test func plainStringIsQuoted() { | ||
| #expect("hello".javaScriptStringLiteral == "\"hello\"") | ||
| } | ||
| ``` |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,26 @@ | ||||||||||||||||||
| import Foundation | ||||||||||||||||||
|
|
||||||||||||||||||
| public extension String { | ||||||||||||||||||
| /// This string encoded as a quoted JavaScript string literal, ready to splice into JS source. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// The value includes the surrounding double quotes and escapes any characters (quotes, | ||||||||||||||||||
| /// backslashes, control characters) that would otherwise break out of the literal. Encoding | ||||||||||||||||||
| /// goes through `JSONSerialization`, so the result is also a valid JSON string. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// ```swift | ||||||||||||||||||
| /// "a\"b".javaScriptStringLiteral // -> "\"a\\\"b\"" | ||||||||||||||||||
| /// webView.evaluateJavaScript("setValue(\(id?.javaScriptStringLiteral ?? "null"))") | ||||||||||||||||||
| /// ``` | ||||||||||||||||||
|
Comment on lines
+10
to
+13
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| /// | ||||||||||||||||||
| /// - Returns: The quoted JS string literal, or `nil` if the string cannot be encoded. | ||||||||||||||||||
| var javaScriptStringLiteral: String? { | ||||||||||||||||||
| // [self] is always valid JSON; reuse JSON's escaping and drop the array brackets to | ||||||||||||||||||
| // get a quoted JS string literal. | ||||||||||||||||||
| guard let data = try? JSONSerialization.data(withJSONObject: [self]), | ||||||||||||||||||
| let arrayLiteral = String(data: data, encoding: .utf8), | ||||||||||||||||||
| arrayLiteral.count >= 2 else { | ||||||||||||||||||
| return nil | ||||||||||||||||||
| } | ||||||||||||||||||
| return String(arrayLiteral.dropFirst().dropLast()) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import Testing | ||
|
|
||
| import CmuxFoundation | ||
|
|
||
| @Suite struct StringJavaScriptStringLiteralTests { | ||
| @Test func plainStringIsQuoted() { | ||
| #expect("hello".javaScriptStringLiteral == "\"hello\"") | ||
| } | ||
|
|
||
| @Test func emptyStringIsEmptyQuotes() { | ||
| #expect("".javaScriptStringLiteral == "\"\"") | ||
| } | ||
|
|
||
| @Test func escapesDoubleQuotesAndBackslashes() { | ||
| #expect(#"a"b\c"#.javaScriptStringLiteral == #""a\"b\\c""#) | ||
| } | ||
|
|
||
| @Test func escapesNewlines() { | ||
| #expect("line1\nline2".javaScriptStringLiteral == #""line1\nline2""#) | ||
| } | ||
|
|
||
| @Test func preservesUnicode() { | ||
| #expect("café".javaScriptStringLiteral == "\"café\"") | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -9,17 +9,7 @@ import WebKit | |||||
| import Combine | ||||||
| import ObjectiveC.runtime | ||||||
| import Darwin | ||||||
|
|
||||||
| func cmuxJavaScriptStringLiteral(_ value: String?) -> String? { | ||||||
| guard let value else { return nil } | ||||||
| // Serialize as a JSON array, then strip the outer brackets to get a quoted JS string literal. | ||||||
| guard let data = try? JSONSerialization.data(withJSONObject: [value]), | ||||||
| let arrayLiteral = String(data: data, encoding: .utf8), | ||||||
| arrayLiteral.count >= 2 else { | ||||||
| return nil | ||||||
| } | ||||||
| return String(arrayLiteral.dropFirst().dropLast()) | ||||||
| } | ||||||
| import CmuxFoundation | ||||||
|
|
||||||
| private struct MultiWindowRouteCLIResult { | ||||||
| let status: String | ||||||
|
|
@@ -10524,7 +10514,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent | |||||
| awaitingInputId: String? = nil, | ||||||
| completion: @escaping ([String: String]) -> Void | ||||||
| ) { | ||||||
| let expectedInputIdLiteral = cmuxJavaScriptStringLiteral(awaitingInputId) ?? "null" | ||||||
| let expectedInputIdLiteral = awaitingInputId?.javaScriptStringLiteral ?? "null" | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The PR deletes
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| let script = """ | ||||||
| (() => { | ||||||
| const expectedInputId = \(expectedInputIdLiteral); | ||||||
|
|
||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
swiftSettingsas the library target. Withswift-tools-version: 6.0, Swift 6 language mode is the default for all targets, soStrictConcurrencyis already active — butExistentialAnyandInternalImportsByDefaultare opt-in upcoming features and will not apply in the test target. Future tests that use bare existentials (Protocolinstead ofany Protocol) or rely on import-visibility semantics will silently diverge from production behavior. Adding the same settings block keeps the compilation environment uniform.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!