Skip to content

Commit e48b532

Browse files
committed
use leading slash
1 parent bb25870 commit e48b532

17 files changed

Lines changed: 79 additions & 43 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ public final class SwiftToSkeleton {
103103
if importedJSModules[path] != nil {
104104
continue
105105
}
106+
guard path.hasPrefix("/") else {
107+
importCollector.errors.append(
108+
DiagnosticError(
109+
node: sourceFile,
110+
message: "JavaScript module paths must start with '/' to indicate the Swift target root: "
111+
+ "'\(path)'."
112+
)
113+
)
114+
continue
115+
}
106116
guard let source = try javaScriptModuleSource(path) else {
107117
importCollector.errors.append(
108118
DiagnosticError(

Plugins/BridgeJS/Sources/BridgeJSLink/ImportedJSModuleRegistry.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ final class ImportedJSModuleRegistry {
1818
var sources: [Key: String] = [:]
1919
for skeleton in skeletons {
2020
for module in skeleton.imported?.modules ?? [] {
21+
guard module.path.hasPrefix("/") else {
22+
throw BridgeJSLinkError(
23+
message: "JavaScript module path must start with '/': \(module.path)"
24+
)
25+
}
2126
let key = Key(swiftModuleName: skeleton.moduleName, path: module.path)
2227
if let existing = sources[key], existing != module.source {
2328
throw BridgeJSLinkError(
@@ -31,7 +36,7 @@ final class ImportedJSModuleRegistry {
3136
for (index, entry) in sources.sorted(by: {
3237
($0.key.swiftModuleName, $0.key.path) < ($1.key.swiftModuleName, $1.key.path)
3338
}).enumerated() {
34-
let relativePath = "bridge-js-modules/\(entry.key.swiftModuleName)/\(entry.key.path)"
39+
let relativePath = "bridge-js-modules/\(entry.key.swiftModuleName)\(entry.key.path)"
3540
aliases[entry.key] = "__bjs_imported_module_\(index)"
3641
artifacts.append(.init(relativePath: relativePath, source: entry.value))
3742
}

Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,8 @@ private func makeJavaScriptModuleFileLookup(
434434
guard fileURL.path.hasPrefix(targetPrefix) else {
435435
throw BridgeJSToolError("JavaScript module is outside the target directory: \(fileURL.path)")
436436
}
437-
let relativePath = String(fileURL.path.dropFirst(targetPrefix.count))
438-
modules[relativePath] = fileURL
437+
let targetRootedPath = "/\(fileURL.path.dropFirst(targetPrefix.count))"
438+
modules[targetRootedPath] = fileURL
439439
}
440440

441441
return modules

Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCodegenTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ import Testing
2626

2727
@Test
2828
func changingOnlyJavaScriptModuleContentsUpdatesGeneratedArtifacts() throws {
29-
let modulePath = "Modules/math.mjs"
29+
let modulePath = "/Modules/math.mjs"
3030
let swiftSource = """
31-
@JSFunction(from: .module("Modules/math.mjs"))
31+
@JSFunction(from: .module("/Modules/math.mjs"))
3232
func add(_ lhs: Int, _ rhs: Int) throws(JSException) -> Int
3333
34-
@JSGetter(jsName: "version", from: .module("Modules/math.mjs"))
34+
@JSGetter(jsName: "version", from: .module("/Modules/math.mjs"))
3535
var moduleVersion: String
3636
"""
3737

@@ -150,7 +150,7 @@ import Testing
150150
moduleName: "TestModule",
151151
exposeToGlobal: false,
152152
externalModuleIndex: .empty,
153-
javaScriptModuleSource: { $0 == "Modules/JSImportModule.mjs" ? moduleSource : nil }
153+
javaScriptModuleSource: { $0 == "/Modules/JSImportModule.mjs" ? moduleSource : nil }
154154
)
155155
swiftAPI.addSourceFile(sourceFile, inputFilePath: input)
156156
let skeleton = try swiftAPI.finalize()

Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ import Testing
5656
return inputs.filter { $0.hasSuffix(`extension`) }
5757
}
5858

59+
@Test
60+
func rejectsNonRootedJavaScriptModulePath() {
61+
let skeleton = BridgeJSSkeleton(
62+
moduleName: "TestModule",
63+
imported: ImportedModuleSkeleton(
64+
children: [],
65+
modules: [ImportedJSModule(path: "Modules/module.js", source: "")]
66+
)
67+
)
68+
#expect(throws: BridgeJSLinkError.self) {
69+
try BridgeJSLink(skeletons: [skeleton]).link()
70+
}
71+
}
72+
5973
@Test(arguments: collectInputs(extension: ".swift"))
6074
func snapshot(input: String) throws {
6175
let url = Self.inputsDirectory.appendingPathComponent(input)
@@ -74,7 +88,7 @@ import Testing
7488
moduleName: "TestModule",
7589
exposeToGlobal: false,
7690
externalModuleIndex: .empty,
77-
javaScriptModuleSource: { $0 == "Modules/JSImportModule.mjs" ? moduleSource : nil }
91+
javaScriptModuleSource: { $0 == "/Modules/JSImportModule.mjs" ? moduleSource : nil }
7892
)
7993
importSwift.addSourceFile(sourceFile, inputFilePath: "\(name).swift")
8094
let importResult = try importSwift.finalize()

Plugins/BridgeJS/Tests/BridgeJSToolTests/DiagnosticsTests.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,16 @@ import Testing
2828

2929
@Test
3030
func missingJavaScriptModuleProducesDiagnostic() throws {
31+
let source = "@JSFunction(from: .module(\"/missing.js\")) func imported() throws(JSException)"
32+
let diagnostics = try #require(moduleDiagnostics(source: source))
33+
#expect(diagnostics.description.contains("JavaScript module file was not found at '/missing.js'"))
34+
}
35+
36+
@Test
37+
func javaScriptModulePathMustStartAtTargetRoot() throws {
3138
let source = "@JSFunction(from: .module(\"missing.js\")) func imported() throws(JSException)"
3239
let diagnostics = try #require(moduleDiagnostics(source: source))
33-
#expect(diagnostics.description.contains("JavaScript module file was not found at 'missing.js'"))
40+
#expect(diagnostics.description.contains("JavaScript module paths must start with '/'"))
3441
}
3542

3643
/// Returns the first parameter's type node from a function in the source (the first `@JS func`-like decl), for pinpointing diagnostics.

Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSImportModule.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
@JSFunction(from: .module("Modules/JSImportModule.mjs"))
1+
@JSFunction(from: .module("/Modules/JSImportModule.mjs"))
22
func moduleAdd(_ lhs: Int, _ rhs: Int) throws(JSException) -> Int
33

4-
@JSFunction(jsName: "renamedFunction", from: .module("Modules/JSImportModule.mjs"))
4+
@JSFunction(jsName: "renamedFunction", from: .module("/Modules/JSImportModule.mjs"))
55
func moduleRenamed() throws(JSException) -> String
66

7-
@JSGetter(jsName: "version", from: .module("Modules/JSImportModule.mjs"))
7+
@JSGetter(jsName: "version", from: .module("/Modules/JSImportModule.mjs"))
88
var moduleVersion: String
99

10-
@JSClass(jsName: "ModuleCounter", from: .module("Modules/JSImportModule.mjs"))
10+
@JSClass(jsName: "ModuleCounter", from: .module("/Modules/JSImportModule.mjs"))
1111
struct ImportedModuleCounter {
1212
@JSFunction init(_ value: Int) throws(JSException)
1313
@JSFunction static func create(_ value: Int) throws(JSException) -> ImportedModuleCounter

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSImportModule.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"isStatic" : false,
1111
"isThrows" : true
1212
},
13-
"from" : "Modules\/JSImportModule.mjs",
13+
"from" : "\/Modules\/JSImportModule.mjs",
1414
"name" : "moduleAdd",
1515
"parameters" : [
1616
{
@@ -52,7 +52,7 @@
5252
"isStatic" : false,
5353
"isThrows" : true
5454
},
55-
"from" : "Modules\/JSImportModule.mjs",
55+
"from" : "\/Modules\/JSImportModule.mjs",
5656
"jsName" : "renamedFunction",
5757
"name" : "moduleRenamed",
5858
"parameters" : [
@@ -68,7 +68,7 @@
6868
"globalGetters" : [
6969
{
7070
"accessLevel" : "internal",
71-
"from" : "Modules\/JSImportModule.mjs",
71+
"from" : "\/Modules\/JSImportModule.mjs",
7272
"jsName" : "version",
7373
"name" : "moduleVersion",
7474
"type" : {
@@ -97,7 +97,7 @@
9797
}
9898
]
9999
},
100-
"from" : "Modules\/JSImportModule.mjs",
100+
"from" : "\/Modules\/JSImportModule.mjs",
101101
"getters" : [
102102
{
103103
"accessLevel" : "internal",
@@ -186,7 +186,7 @@
186186
],
187187
"modules" : [
188188
{
189-
"path" : "Modules\/JSImportModule.mjs",
189+
"path" : "\/Modules\/JSImportModule.mjs",
190190
"source" : "export function moduleAdd(lhs, rhs) {\n return lhs + rhs;\n}\n\nexport function renamedFunction() {\n return \"renamed\";\n}\n\nexport const version = \"1.0\";\n\nexport class ModuleCounter {\n constructor(value) {\n this.value = value;\n }\n\n static create(value) {\n return new ModuleCounter(value);\n }\n\n increment() {\n return ++this.value;\n }\n\n setValue(value) {\n this.value = value;\n }\n}\n"
191191
}
192192
]

Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-JavaScript-into-Swift.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ You can bring JavaScript into Swift in three ways:
2929

3030
- **Inject at initialization**: Declare in Swift and supply the implementation in `getImports()` (e.g. a `today()` function).
3131
- **Import from `globalThis`**: For APIs on the JavaScript global object (e.g. `console`, `document`), use `@JSGetter(from: .global)` so they are read from `globalThis` and you don't pass them in `getImports()`.
32-
- **Ship an ECMAScript module**: Use `from: .module("target/relative/path.js")` on a top-level function/getter or `@JSClass`. BridgeJS copies the referenced file into the generated package, so it is not supplied through `getImports()`.
32+
- **Ship an ECMAScript module**: Use `from: .module("/path/from/target/root.js")` on a top-level function/getter or `@JSClass`. The leading `/` denotes the Swift target root; it is not a filesystem-absolute path. BridgeJS copies the referenced file into the generated package, so it is not supplied through `getImports()`.
3333

3434
```swift
3535
import JavaScriptKit

Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-JavaScript/Importing-JS-Class.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ export class Greeter {
3636
```
3737

3838
```swift
39-
@JSClass(from: .module("JavaScript/greeter.js"))
39+
@JSClass(from: .module("/JavaScript/greeter.js"))
4040
struct Greeter {
4141
@JSFunction init(_ name: String) throws(JSException)
4242
@JSFunction static func named(_ name: String) throws(JSException) -> Greeter
4343
@JSFunction func greet() throws(JSException) -> String
4444
}
4545
```
4646

47-
The module's named class export is the root for construction and static methods. Instance methods, getters, and setters operate on the wrapped object and must not specify their own `from:` argument. Use `jsName` on `@JSClass` to select a differently named class export. JavaScript inheritance may be implemented normally in the module; the Swift declaration describes the API visible on the exported class and its instances.
47+
The path's leading `/` denotes the Swift target root, not the filesystem root. The module's named class export is the root for construction and static methods. Instance methods, getters, and setters operate on the wrapped object and must not specify their own `from:` argument. Use `jsName` on `@JSClass` to select a differently named class export. JavaScript inheritance may be implemented normally in the module; the Swift declaration describes the API visible on the exported class and its instances.
4848

4949
### 2. Wire the JavaScript side
5050

0 commit comments

Comments
 (0)