From 0c6ae4fa9e95060ab47fb1cd42e07bcf1e4f7ce4 Mon Sep 17 00:00:00 2001 From: --add Date: Sun, 17 Nov 2024 22:06:21 -0600 Subject: [PATCH 1/7] added filter function to config --- pkgs/swift2objc/lib/src/config.dart | 10 ++++++++++ pkgs/swift2objc/lib/src/generate_wrapper.dart | 3 ++- .../parse_variable_declaration.dart | 10 ++++++++++ .../lib/src/transformer/transform.dart | 16 +++++++++++----- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/pkgs/swift2objc/lib/src/config.dart b/pkgs/swift2objc/lib/src/config.dart index effff1d27..37b57514a 100644 --- a/pkgs/swift2objc/lib/src/config.dart +++ b/pkgs/swift2objc/lib/src/config.dart @@ -1,5 +1,7 @@ import 'package:path/path.dart' as path; +import 'ast/_core/interfaces/declaration.dart'; + const defaultTempDirPrefix = 'swift2objc_temp_'; const symbolgraphFileSuffix = '.symbols.json'; @@ -32,11 +34,19 @@ class Config { /// intermediate files after generating the wrapper final Uri? tempDir; + /// Filter function to filter APIs + /// + /// APIs can be filtered by name + /// + /// TODO: Add `excludeAll` option to exclude all or include all declarations + final bool Function(Declaration declaration)? filter; + const Config({ required this.input, required this.outputFile, this.tempDir, this.preamble, + this.filter }); } diff --git a/pkgs/swift2objc/lib/src/generate_wrapper.dart b/pkgs/swift2objc/lib/src/generate_wrapper.dart index 2cc9f0380..01d1b8332 100644 --- a/pkgs/swift2objc/lib/src/generate_wrapper.dart +++ b/pkgs/swift2objc/lib/src/generate_wrapper.dart @@ -34,7 +34,8 @@ Future generateWrapper(Config config) async { final symbolgraphJsonPath = path.join(tempDir.path, symbolgraphFileName); final declarations = parseAst(symbolgraphJsonPath); - final transformedDeclarations = transform(declarations); + final transformedDeclarations = transform(declarations, + filter: config.filter); final wrapperCode = generate(transformedDeclarations, config.preamble); File.fromUri(config.outputFile).writeAsStringSync(wrapperCode); diff --git a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart index 783c6bb48..f076a24c1 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart @@ -43,6 +43,16 @@ ReferredType _parseVariableType( ) { final subHeadings = propertySymbolJson['names']['subHeading']; + // if subheadings have text that contain sets of parentheses and arrows + // like ":()->" or ":(" and ")->", then typeIdentifiers can be misleading + final identifier = subHeadings.firstJsonWhereKey('kind', 'identifier'); + final trueTypeItems = subHeadings.skip( + subHeadings.toList().indexOf(identifier) + ); + + // parse type + + // else go ahead final typeSymbolJson = subHeadings.firstJsonWhereKey('kind', 'typeIdentifier'); final typeSymbolId = typeSymbolJson['preciseIdentifier'].get(); diff --git a/pkgs/swift2objc/lib/src/transformer/transform.dart b/pkgs/swift2objc/lib/src/transformer/transform.dart index e7d4618c5..e90db0406 100644 --- a/pkgs/swift2objc/lib/src/transformer/transform.dart +++ b/pkgs/swift2objc/lib/src/transformer/transform.dart @@ -13,20 +13,26 @@ import 'transformers/transform_globals.dart'; typedef TransformationMap = Map; -List transform(List declarations) { +/// Transforms the given declarations into the desired ObjC wrapped declarations +List transform(List declarations, { + bool Function(Declaration)? filter +}) { final TransformationMap transformationMap; + final _filter = filter ?? (declaration) => true; + + final _declarations = declarations.where((d) => _filter(d)); transformationMap = {}; final globalNamer = UniqueNamer( - declarations.map((declaration) => declaration.name), + _declarations.map((declaration) => declaration.name), ); final globals = Globals( - functions: declarations.whereType().toList(), - variables: declarations.whereType().toList(), + functions: _declarations.whereType().toList(), + variables: _declarations.whereType().toList(), ); - final nonGlobals = declarations + final nonGlobals = _declarations .where( (declaration) => declaration is! GlobalFunctionDeclaration && From 6b42b1c62e933f076a5d5f367677a931376e7f08 Mon Sep 17 00:00:00 2001 From: --add Date: Sun, 17 Nov 2024 23:05:35 -0600 Subject: [PATCH 2/7] unit tests for filter --- pkgs/swift2objc/test/unit/filter_test.dart | 58 +++++++++++++++++++ .../test/unit/filter_test_input.swift | 13 +++++ .../test/unit/filter_test_output.swift | 3 + 3 files changed, 74 insertions(+) create mode 100644 pkgs/swift2objc/test/unit/filter_test.dart create mode 100644 pkgs/swift2objc/test/unit/filter_test_input.swift create mode 100644 pkgs/swift2objc/test/unit/filter_test_output.swift diff --git a/pkgs/swift2objc/test/unit/filter_test.dart b/pkgs/swift2objc/test/unit/filter_test.dart new file mode 100644 index 000000000..ee73a8630 --- /dev/null +++ b/pkgs/swift2objc/test/unit/filter_test.dart @@ -0,0 +1,58 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:swift2objc/swift2objc.dart'; +import 'package:test/test.dart'; + +void main() { + group('Unit test for filter', () { + final thisDir = path.join(Directory.current.path, 'test/unit'); + + final file = path.join(thisDir, 'filter_test_input.swift'); + final output = path.join(thisDir, 'filter_test_output.swift'); + test(path.basename(file), () async { + final actualOutputFile = path.join(thisDir, path.basename(output)); + + await generateWrapper(Config( + input: FilesInputConfig( + files: [Uri.file(file)], + ), + outputFile: Uri.file(actualOutputFile), + tempDir: Directory(thisDir).uri, + preamble: '// Test preamble text', + filter: (declaration) => false, + )); + + final actualOutput = await File(actualOutputFile).readAsString(); + final expectedOutput = File(output).readAsStringSync(); + + expect(actualOutput, expectedOutput); + } + ); + + tearDown(() { + if (File(path.join(thisDir, 'symbolgraph_module.abi.json')).existsSync()) { + File(path.join(thisDir, 'symbolgraph_module.abi.json')).deleteSync(); + } + if (File(path.join(thisDir, 'symbolgraph_module.swiftdoc')).existsSync()) { + File(path.join(thisDir, 'symbolgraph_module.swiftdoc')).deleteSync(); + } + if (File(path.join(thisDir, 'symbolgraph_module.swiftmodule')).existsSync()) { + File(path.join(thisDir, 'symbolgraph_module.swiftmodule')).deleteSync(); + } + if (File(path.join(thisDir, 'symbolgraph_module.swiftsource')).existsSync()) { + File(path.join(thisDir, 'symbolgraph_module.swiftsource')).deleteSync(); + } + if (File(path.join(thisDir, 'symbolgraph_module.symbols.json')).existsSync()) { + File(path.join(thisDir, 'symbolgraph_module.symbols.json')).deleteSync(); + } + if (File(path.join(thisDir, 'symbolgraph_module.swiftsourceinfo')).existsSync()) { + File(path.join(thisDir, 'symbolgraph_module.swiftsourceinfo')).deleteSync(); + } + }); + }); +} \ No newline at end of file diff --git a/pkgs/swift2objc/test/unit/filter_test_input.swift b/pkgs/swift2objc/test/unit/filter_test_input.swift new file mode 100644 index 000000000..e8ce0fbab --- /dev/null +++ b/pkgs/swift2objc/test/unit/filter_test_input.swift @@ -0,0 +1,13 @@ +import Foundation + +public class MyClass { + public let representableProperty: Int + public let customProperty: MyOtherClass + + public init(outerLabel representableProperty: Int, customProperty: MyOtherClass) { + self.representableProperty = representableProperty + self.customProperty = customProperty + } +} + +public class MyOtherClass {} diff --git a/pkgs/swift2objc/test/unit/filter_test_output.swift b/pkgs/swift2objc/test/unit/filter_test_output.swift new file mode 100644 index 000000000..53226acbd --- /dev/null +++ b/pkgs/swift2objc/test/unit/filter_test_output.swift @@ -0,0 +1,3 @@ +// Test preamble text + +import Foundation From 2af70244fc9eb7ddb525efad641c50397c3b0059 Mon Sep 17 00:00:00 2001 From: --add Date: Mon, 18 Nov 2024 20:38:57 -0600 Subject: [PATCH 3/7] updated config options --- pkgs/swift2objc/lib/src/config.dart | 8 +++++--- pkgs/swift2objc/lib/src/generate_wrapper.dart | 9 +++------ .../parse_variable_declaration.dart | 10 ---------- pkgs/swift2objc/test/unit/filter_test.dart | 2 +- 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/pkgs/swift2objc/lib/src/config.dart b/pkgs/swift2objc/lib/src/config.dart index 8633ca5da..fffafcd08 100644 --- a/pkgs/swift2objc/lib/src/config.dart +++ b/pkgs/swift2objc/lib/src/config.dart @@ -38,15 +38,17 @@ class Config { /// /// APIs can be filtered by name /// - /// TODO: Add `excludeAll` option to exclude all or include all declarations - final bool Function(Declaration declaration)? filter; + /// Includes all declarations by default + final bool Function(Declaration declaration)? include; + + static bool _defaultInclude(_) => true; const Config({ required this.input, required this.outputFile, this.tempDir, this.preamble, - this.filter + this.include = Config._defaultInclude }); } diff --git a/pkgs/swift2objc/lib/src/generate_wrapper.dart b/pkgs/swift2objc/lib/src/generate_wrapper.dart index 5b0d3008a..674869071 100644 --- a/pkgs/swift2objc/lib/src/generate_wrapper.dart +++ b/pkgs/swift2objc/lib/src/generate_wrapper.dart @@ -45,18 +45,15 @@ Future generateWrapper(Config config) async { JsonFileInputConfig() => parseModuleName(symbolgraphJson), }; + final declarations = parseAst(symbolgraphJson); - final transformedDeclarations = transform(declarations); - + final transformedDeclarations = transform(declarations, + filter: config.include); final wrapperCode = generate( transformedDeclarations, moduleName: sourceModule, preamble: config.preamble, ); - final declarations = parseAst(symbolgraphJsonPath); - final transformedDeclarations = transform(declarations, - filter: config.filter); - final wrapperCode = generate(transformedDeclarations, config.preamble); File.fromUri(config.outputFile).writeAsStringSync(wrapperCode); diff --git a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart index f076a24c1..783c6bb48 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_variable_declaration.dart @@ -43,16 +43,6 @@ ReferredType _parseVariableType( ) { final subHeadings = propertySymbolJson['names']['subHeading']; - // if subheadings have text that contain sets of parentheses and arrows - // like ":()->" or ":(" and ")->", then typeIdentifiers can be misleading - final identifier = subHeadings.firstJsonWhereKey('kind', 'identifier'); - final trueTypeItems = subHeadings.skip( - subHeadings.toList().indexOf(identifier) - ); - - // parse type - - // else go ahead final typeSymbolJson = subHeadings.firstJsonWhereKey('kind', 'typeIdentifier'); final typeSymbolId = typeSymbolJson['preciseIdentifier'].get(); diff --git a/pkgs/swift2objc/test/unit/filter_test.dart b/pkgs/swift2objc/test/unit/filter_test.dart index ee73a8630..6cecbf765 100644 --- a/pkgs/swift2objc/test/unit/filter_test.dart +++ b/pkgs/swift2objc/test/unit/filter_test.dart @@ -24,7 +24,7 @@ void main() { outputFile: Uri.file(actualOutputFile), tempDir: Directory(thisDir).uri, preamble: '// Test preamble text', - filter: (declaration) => false, + include: (declaration) => false, )); final actualOutput = await File(actualOutputFile).readAsString(); From 34d05ed6f233ec63937238d49d209305e8dfdef3 Mon Sep 17 00:00:00 2001 From: --add Date: Wed, 20 Nov 2024 16:16:32 -0600 Subject: [PATCH 4/7] reimplemented unit tests for filtering --- pkgs/swift2objc/test/unit/filter_test.dart | 64 ++++++- .../test/unit/filter_test_input.swift | 136 +++++++++++++- .../test/unit/filter_test_output_a.swift | 31 +++ .../test/unit/filter_test_output_b.swift | 177 ++++++++++++++++++ ...utput.swift => filter_test_output_c.swift} | 0 5 files changed, 395 insertions(+), 13 deletions(-) create mode 100644 pkgs/swift2objc/test/unit/filter_test_output_a.swift create mode 100644 pkgs/swift2objc/test/unit/filter_test_output_b.swift rename pkgs/swift2objc/test/unit/{filter_test_output.swift => filter_test_output_c.swift} (100%) diff --git a/pkgs/swift2objc/test/unit/filter_test.dart b/pkgs/swift2objc/test/unit/filter_test.dart index 6cecbf765..b43e5f8c6 100644 --- a/pkgs/swift2objc/test/unit/filter_test.dart +++ b/pkgs/swift2objc/test/unit/filter_test.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:path/path.dart' as path; +import 'package:swift2objc/src/ast/declarations/compounds/class_declaration.dart'; import 'package:swift2objc/swift2objc.dart'; import 'package:test/test.dart'; @@ -13,9 +14,11 @@ void main() { final thisDir = path.join(Directory.current.path, 'test/unit'); final file = path.join(thisDir, 'filter_test_input.swift'); - final output = path.join(thisDir, 'filter_test_output.swift'); - test(path.basename(file), () async { - final actualOutputFile = path.join(thisDir, path.basename(output)); + test('A: Specific Files', () async { + final output = path.join(thisDir, 'filter_test_output_a.swift'); + final actualOutputFile = path.join(thisDir, '${ + path.basenameWithoutExtension(output)}.test${path.extension(output) + }'); await generateWrapper(Config( input: FilesInputConfig( @@ -24,15 +27,60 @@ void main() { outputFile: Uri.file(actualOutputFile), tempDir: Directory(thisDir).uri, preamble: '// Test preamble text', - include: (declaration) => false, + include: (declaration) => declaration.name == 'Engine', )); final actualOutput = await File(actualOutputFile).readAsString(); final expectedOutput = File(output).readAsStringSync(); expect(actualOutput, expectedOutput); - } - ); + }); + + test('B: Declarations of a specific type', () async { + final output = path.join(thisDir, 'filter_test_output_b.swift'); + final actualOutputFile = path.join(thisDir, '${ + path.basenameWithoutExtension(output)}.test${path.extension(output) + }'); + + await generateWrapper(Config( + input: FilesInputConfig( + files: [Uri.file(file)], + ), + outputFile: Uri.file(actualOutputFile), + tempDir: Directory(thisDir).uri, + preamble: '// Test preamble text', + include: (declaration) => declaration is ClassDeclaration, + )); + + final actualOutput = await File(actualOutputFile).readAsString(); + final expectedOutput = File(output).readAsStringSync(); + + expect(actualOutput, expectedOutput); + }); + + test('C: Nonexistent declaration', () async { + final output = path.join(thisDir, 'filter_test_output_c.swift'); + final actualOutputFile = path.join(thisDir, '${ + path.basenameWithoutExtension(output)}.test${path.extension(output) + }'); + + await generateWrapper(Config( + input: FilesInputConfig( + files: [Uri.file(file)], + ), + outputFile: Uri.file(actualOutputFile), + tempDir: Directory(thisDir).uri, + preamble: '// Test preamble text', + // The following declaration does not exist, + // so none are produced in output + include: (declaration) => declaration.name == 'Ship', + )); + + final actualOutput = await File(actualOutputFile).readAsString(); + final expectedOutput = File(output).readAsStringSync(); + + expect(actualOutput, expectedOutput); + }); tearDown(() { if (File(path.join(thisDir, 'symbolgraph_module.abi.json')).existsSync()) { @@ -53,6 +101,10 @@ void main() { if (File(path.join(thisDir, 'symbolgraph_module.swiftsourceinfo')).existsSync()) { File(path.join(thisDir, 'symbolgraph_module.swiftsourceinfo')).deleteSync(); } + + for (final file in Directory(thisDir).listSync().where((t) => path.extension(t.path, 2) == '.test.swift')) { + if (file is File) file.deleteSync(); + } }); }); } \ No newline at end of file diff --git a/pkgs/swift2objc/test/unit/filter_test_input.swift b/pkgs/swift2objc/test/unit/filter_test_input.swift index e8ce0fbab..40992e06a 100644 --- a/pkgs/swift2objc/test/unit/filter_test_input.swift +++ b/pkgs/swift2objc/test/unit/filter_test_input.swift @@ -1,13 +1,135 @@ import Foundation -public class MyClass { - public let representableProperty: Int - public let customProperty: MyOtherClass +public struct Engine { + public let type: String + public let horsepower: Int - public init(outerLabel representableProperty: Int, customProperty: MyOtherClass) { - self.representableProperty = representableProperty - self.customProperty = customProperty + public init(type: String, horsepower: Int) { + self.type = type + self.horsepower = horsepower + } + + public func displaySpecs() { + print("Engine: \(type), \(horsepower) HP") + } +} + + +public struct Tire { + public let brand: String + public let size: Int + + public init(brand: String, size: Int) { + self.brand = brand + self.size = size + } + + public func displayInfo() { + print("Tire: \(brand), size \(size)") + } +} + + +public struct Dimensions { + public let length: Double + public let width: Double + public let height: Double + + public init(length: Double, width: Double, height: Double) { + self.length = length + self.width = width + self.height = height + } + + public func displayDimensions() { + print("Dimensions (LxWxH): \(length) x \(width) x \(height) meters") + } +} + + +public class Vehicle { + public var make: String + public var model: String + public var engine: Engine + public var dimensions: Dimensions + + public init(make: String, model: String, engine: Engine, dimensions: Dimensions) { + self.make = make + self.model = model + self.engine = engine + self.dimensions = dimensions + } + + public func displayInfo() { + print("Vehicle: \(make) \(model)") + engine.displaySpecs() + dimensions.displayDimensions() + } +} + + +public class Car: Vehicle { + public var numberOfDoors: Int + public var tires: [Tire] + + public init(make: String, model: String, engine: Engine, dimensions: Dimensions, numberOfDoors: Int, tires: [Tire]) { + self.numberOfDoors = numberOfDoors + self.tires = tires + super.init(make: make, model: model, engine: engine, dimensions: dimensions) + } + + public func honk() { + print("Car \(make) \(model) goes 'Beep Beep!'") } } -public class MyOtherClass {} + +public class ElectricCar: Car { + public var batteryCapacity: Int // in kWh + + public init(make: String, model: String, dimensions: Dimensions, numberOfDoors: Int, tires: [Tire], batteryCapacity: Int) { + self.batteryCapacity = batteryCapacity + let electricEngine = Engine(type: "Electric", horsepower: batteryCapacity * 3) // Example calculation + super.init(make: make, model: model, engine: electricEngine, dimensions: dimensions, numberOfDoors: numberOfDoors, tires: tires) + } + + public func chargeBattery() { + print("Charging \(make) \(model)... Battery capacity: \(batteryCapacity) kWh") + } +} + +public class Bicycle { + public var brand: String + public var gearCount: Int + public var dimensions: Dimensions + + public init(brand: String, gearCount: Int, dimensions: Dimensions) { + self.brand = brand + self.gearCount = gearCount + self.dimensions = dimensions + } + + public func pedal() { + print("\(brand) bicycle is pedaling with \(gearCount) gears.") + dimensions.displayDimensions() + } +} + + +public class Garage { + private var vehicles: [Vehicle] = [] + + public init() {} + + public func addVehicle(_ vehicle: Vehicle) { + vehicles.append(vehicle) + print("Added \(vehicle.make) \(vehicle.model) to the garage.") + } + + public func listVehicles() { + print("Garage contains:") + for vehicle in vehicles { + print("- \(vehicle.make) \(vehicle.model)") + } + } +} diff --git a/pkgs/swift2objc/test/unit/filter_test_output_a.swift b/pkgs/swift2objc/test/unit/filter_test_output_a.swift new file mode 100644 index 000000000..971cabeab --- /dev/null +++ b/pkgs/swift2objc/test/unit/filter_test_output_a.swift @@ -0,0 +1,31 @@ +// Test preamble text + +import Foundation + +@objc public class EngineWrapper: NSObject { + var wrappedInstance: Engine + + @objc public var horsepower: Int { + get { + wrappedInstance.horsepower + } + } + + @objc public var type: String { + get { + wrappedInstance.type + } + } + + init(_ wrappedInstance: Engine) { + self.wrappedInstance = wrappedInstance + } + + @objc init(type: String, horsepower: Int) { + wrappedInstance = Engine(type: type, horsepower: horsepower) + } + + @objc public func displaySpecs() { + wrappedInstance.displaySpecs() + } +} diff --git a/pkgs/swift2objc/test/unit/filter_test_output_b.swift b/pkgs/swift2objc/test/unit/filter_test_output_b.swift new file mode 100644 index 000000000..38f13419a --- /dev/null +++ b/pkgs/swift2objc/test/unit/filter_test_output_b.swift @@ -0,0 +1,177 @@ +// Test preamble text + +import Foundation + +@objc public class ElectricCarWrapper: NSObject { + var wrappedInstance: ElectricCar + + @objc public var batteryCapacity: Int { + get { + wrappedInstance.batteryCapacity + } + set { + wrappedInstance.batteryCapacity = newValue + } + } + + init(_ wrappedInstance: ElectricCar) { + self.wrappedInstance = wrappedInstance + } + + @objc init(make: String, model: String, dimensions: DimensionsWrapper, numberOfDoors: Int, tires: TireWrapper, batteryCapacity: Int) { + wrappedInstance = ElectricCar(make: make, model: model, dimensions: dimensions.wrappedInstance, numberOfDoors: numberOfDoors, tires: tires.wrappedInstance, batteryCapacity: batteryCapacity) + } + + @objc public func chargeBattery() { + wrappedInstance.chargeBattery() + } +} + +@objc public class CarWrapper: NSObject { + var wrappedInstance: Car + + @objc public var numberOfDoors: Int { + get { + wrappedInstance.numberOfDoors + } + set { + wrappedInstance.numberOfDoors = newValue + } + } + + @objc public var tires: TireWrapper { + get { + TireWrapper(wrappedInstance.tires) + } + set { + wrappedInstance.tires = newValue.wrappedInstance + } + } + + init(_ wrappedInstance: Car) { + self.wrappedInstance = wrappedInstance + } + + @objc init(make: String, model: String, engine: EngineWrapper, dimensions: DimensionsWrapper, numberOfDoors: Int, tires: TireWrapper) { + wrappedInstance = Car(make: make, model: model, engine: engine.wrappedInstance, dimensions: dimensions.wrappedInstance, numberOfDoors: numberOfDoors, tires: tires.wrappedInstance) + } + + @objc public func honk() { + wrappedInstance.honk() + } +} + +@objc public class GarageWrapper: NSObject { + var wrappedInstance: Garage + + init(_ wrappedInstance: Garage) { + self.wrappedInstance = wrappedInstance + } + + @objc override init() { + wrappedInstance = Garage() + } + + @objc public func addVehicle(vehicle: VehicleWrapper) { + wrappedInstance.addVehicle(vehicle: vehicle.wrappedInstance) + } + + @objc public func listVehicles() { + wrappedInstance.listVehicles() + } +} + +@objc public class BicycleWrapper: NSObject { + var wrappedInstance: Bicycle + + @objc public var dimensions: DimensionsWrapper { + get { + DimensionsWrapper(wrappedInstance.dimensions) + } + set { + wrappedInstance.dimensions = newValue.wrappedInstance + } + } + + @objc public var brand: String { + get { + wrappedInstance.brand + } + set { + wrappedInstance.brand = newValue + } + } + + @objc public var gearCount: Int { + get { + wrappedInstance.gearCount + } + set { + wrappedInstance.gearCount = newValue + } + } + + init(_ wrappedInstance: Bicycle) { + self.wrappedInstance = wrappedInstance + } + + @objc init(brand: String, gearCount: Int, dimensions: DimensionsWrapper) { + wrappedInstance = Bicycle(brand: brand, gearCount: gearCount, dimensions: dimensions.wrappedInstance) + } + + @objc public func pedal() { + wrappedInstance.pedal() + } +} + +@objc public class VehicleWrapper: NSObject { + var wrappedInstance: Vehicle + + @objc public var dimensions: DimensionsWrapper { + get { + DimensionsWrapper(wrappedInstance.dimensions) + } + set { + wrappedInstance.dimensions = newValue.wrappedInstance + } + } + + @objc public var make: String { + get { + wrappedInstance.make + } + set { + wrappedInstance.make = newValue + } + } + + @objc public var model: String { + get { + wrappedInstance.model + } + set { + wrappedInstance.model = newValue + } + } + + @objc public var engine: EngineWrapper { + get { + EngineWrapper(wrappedInstance.engine) + } + set { + wrappedInstance.engine = newValue.wrappedInstance + } + } + + init(_ wrappedInstance: Vehicle) { + self.wrappedInstance = wrappedInstance + } + + @objc init(make: String, model: String, engine: EngineWrapper, dimensions: DimensionsWrapper) { + wrappedInstance = Vehicle(make: make, model: model, engine: engine.wrappedInstance, dimensions: dimensions.wrappedInstance) + } + + @objc public func displayInfo() { + wrappedInstance.displayInfo() + } +} diff --git a/pkgs/swift2objc/test/unit/filter_test_output.swift b/pkgs/swift2objc/test/unit/filter_test_output_c.swift similarity index 100% rename from pkgs/swift2objc/test/unit/filter_test_output.swift rename to pkgs/swift2objc/test/unit/filter_test_output_c.swift From dbe4ba472fa01710bdb215d688a76b852a7154da Mon Sep 17 00:00:00 2001 From: --add Date: Mon, 2 Dec 2024 00:48:10 -0600 Subject: [PATCH 5/7] updated filtering to include transitive dependencies --- pkgs/swift2objc/lib/src/config.dart | 19 +- pkgs/swift2objc/lib/src/generate_wrapper.dart | 5 +- .../src/transformer/_core/dependencies.dart | 198 ++++++++++++++++++ .../lib/src/transformer/transform.dart | 19 +- pkgs/swift2objc/test/unit/filter_test.dart | 163 +++++++------- .../test/unit/filter_test_output_a.swift | 14 +- .../test/unit/filter_test_output_b.swift | 194 +++++++++++++---- .../test/unit/filter_test_output_c.swift | 1 + pkgs/swift2objc/test/utils/utils.dart | 9 + 9 files changed, 477 insertions(+), 145 deletions(-) create mode 100644 pkgs/swift2objc/lib/src/transformer/_core/dependencies.dart create mode 100644 pkgs/swift2objc/test/utils/utils.dart diff --git a/pkgs/swift2objc/lib/src/config.dart b/pkgs/swift2objc/lib/src/config.dart index fffafcd08..2d7f55d0f 100644 --- a/pkgs/swift2objc/lib/src/config.dart +++ b/pkgs/swift2objc/lib/src/config.dart @@ -34,22 +34,21 @@ class Config { /// intermediate files after generating the wrapper final Uri? tempDir; - /// Filter function to filter APIs - /// + /// Filter function to filter APIs + /// /// APIs can be filtered by name - /// + /// /// Includes all declarations by default final bool Function(Declaration declaration)? include; static bool _defaultInclude(_) => true; - const Config({ - required this.input, - required this.outputFile, - this.tempDir, - this.preamble, - this.include = Config._defaultInclude - }); + const Config( + {required this.input, + required this.outputFile, + this.tempDir, + this.preamble, + this.include = Config._defaultInclude}); } /// Used to specify the inputs in the `config` object. diff --git a/pkgs/swift2objc/lib/src/generate_wrapper.dart b/pkgs/swift2objc/lib/src/generate_wrapper.dart index 674869071..bfe84aa70 100644 --- a/pkgs/swift2objc/lib/src/generate_wrapper.dart +++ b/pkgs/swift2objc/lib/src/generate_wrapper.dart @@ -45,10 +45,9 @@ Future generateWrapper(Config config) async { JsonFileInputConfig() => parseModuleName(symbolgraphJson), }; - final declarations = parseAst(symbolgraphJson); - final transformedDeclarations = transform(declarations, - filter: config.include); + final transformedDeclarations = + transform(declarations, filter: config.include); final wrapperCode = generate( transformedDeclarations, moduleName: sourceModule, diff --git a/pkgs/swift2objc/lib/src/transformer/_core/dependencies.dart b/pkgs/swift2objc/lib/src/transformer/_core/dependencies.dart new file mode 100644 index 000000000..cd971a8da --- /dev/null +++ b/pkgs/swift2objc/lib/src/transformer/_core/dependencies.dart @@ -0,0 +1,198 @@ +import '../../ast/_core/interfaces/declaration.dart'; +import '../../ast/_core/interfaces/enum_declaration.dart'; +import '../../ast/_core/interfaces/function_declaration.dart'; +import '../../ast/_core/interfaces/variable_declaration.dart'; +import '../../ast/_core/shared/parameter.dart'; +import '../../ast/_core/shared/referred_type.dart'; +import '../../ast/declarations/compounds/class_declaration.dart'; +import '../../ast/declarations/compounds/members/initializer_declaration.dart'; +import '../../ast/declarations/compounds/protocol_declaration.dart'; +import '../../ast/declarations/compounds/struct_declaration.dart'; + +/// Gets the type name from a string type by removing other characters like +Set _getTypeNames(String type) { + // Remove optional markers (?) and square brackets ([]) + type = type.replaceAll(RegExp(r'\?|!|[\[\]]'), ''); + + // Remove annotations (words starting with @) + type = type.replaceAll(RegExp(r'@\w+'), ''); + + // Extract unique type names using regex + final matches = RegExp(r'\b\w+\b').allMatches(type); + + // Return unique type names as a set + return matches.map((match) => match.group(0)!).toSet(); +} + +// TODO: Type restrictions have not yet been implemented in system +class DependencyVisitor { + Set visitDeclaration(Declaration decl, [Set? context]) { + final cont = context ??= {}; + + // switch between declarations + if (decl is ClassDeclaration) + visitClass(decl, cont); + else if (decl is ProtocolDeclaration) + visitProtocol(decl, cont); + else if (decl is StructDeclaration) + visitStruct(decl, cont); + else if (decl is FunctionDeclaration) + visitFunction(decl, cont); + else if (decl is VariableDeclaration) + visitVariable(decl, cont); + else if (decl is EnumDeclaration) visitEnum(decl, cont); + + return cont; + } + + Set visitEnum(EnumDeclaration decl, [Set? context]) { + final cont = context ??= {}; + + // TODO: what of raw values of enums? + + // visit nested declarations + decl.nestedDeclarations.forEach((n) => visitDeclaration(n, cont)); + + // visit protocols + decl.conformedProtocols.forEach((p) => visitProtocol(p.declaration, cont)); + + // ensure generic types do not enter + cont.removeWhere((t) => decl.typeParams.map((type) => type.name).contains(t)); + + return cont; + } + + Set visitStruct(StructDeclaration decl, [Set? context]) { + final cont = context ??= {}; + + // visit variables + decl.properties.forEach((d) => visitVariable(d, cont)); + + // visit methods + decl.methods.forEach((m) => visitFunction(m, cont)); + + // visit initializers + decl.initializers.forEach((i) => visitInitializer(i, cont)); + + // visit nested declarations + decl.nestedDeclarations.forEach((n) => visitDeclaration(n, cont)); + + // visit protocols + decl.conformedProtocols.forEach((p) => visitProtocol(p.declaration, cont)); + + // ensure generic types do not enter + cont.removeWhere((t) => decl.typeParams.map((type) => type.name).contains(t)); + + return cont; + } + + Set visitClass(ClassDeclaration decl, [Set? context]) { + final cont = context ??= {}; + + // visit variables + decl.properties.forEach((d) => visitVariable(d, cont)); + + // visit methods + decl.methods.forEach((m) => visitFunction(m, cont)); + + // visit initializers + decl.initializers.forEach((i) => visitInitializer(i, cont)); + + // visit super if any + if (decl.superClass != null) + visitDeclaration(decl.superClass!.declaration, cont); + + // visit nested declarations + decl.nestedDeclarations.forEach((n) => visitDeclaration(n, cont)); + + // visit protocols + decl.conformedProtocols.forEach((p) => visitProtocol(p.declaration, cont)); + + // ensure generic types do not enter + cont.removeWhere((t) => decl.typeParams.map((type) => type.name).contains(t)); + + return cont; + } + + Set visitProtocol(ProtocolDeclaration decl, [Set? context]) { + final cont = context ??= {}; + + // visit variables + decl.properties.forEach((d) => visitVariable(d, cont)); + + // visit methods + decl.methods.forEach((m) => visitFunction(m, cont)); + + // visit initializers + decl.initializers.forEach((i) => visitInitializer(i, cont)); + + // visit nested declarations + decl.nestedDeclarations.forEach((n) => visitDeclaration(n, cont)); + + // visit protocols + decl.conformedProtocols.forEach((p) => visitProtocol(p.declaration, cont)); + + // ensure generic types do not enter + cont.removeWhere((t) => decl.typeParams.map((type) => type.name).contains(t)); + + return cont; + } + + Set visitInitializer(InitializerDeclaration decl, + [Set? context]) { + final cont = context ??= {}; + + // similar to `visitMethod`, except no return type + decl.params.forEach((p) => visitParameter(p, cont)); + + return cont; + } + + Set visitFunction(FunctionDeclaration decl, [Set? context]) { + final cont = context ??= {}; + + // visit parameters + decl.params.forEach((p) => visitParameter(p, cont)); + + // ensure generic types do not enter + cont.removeWhere((t) => decl.typeParams.map((type) => type.name).contains(t)); + + // visit return type + visitType(decl.returnType, cont); + + // TODO: what of type restrictions (`... where T.Element: CustomStringConvertible`) + + return cont; + } + + Set visitParameter(Parameter decl, [Set? context]) { + final cont = context ??= {}; + + // just visit type of parameter + visitType(decl.type, cont); + + return cont; + } + + Set visitVariable(VariableDeclaration decl, [Set? context]) { + final cont = context ??= {}; + + // just return property type + visitType(decl.type, cont); + + return cont; + } + + Set visitType(ReferredType type, [Set? context]) { + final cont = context ??= {}; + + // we need to confirm the types located + // at the moment, we can perform simple regex to clean up text characters + + // since we are making such visitations on normal declarations in a file, + // we do not need to filter out primitives at the moment + cont.addAll(_getTypeNames(type.swiftType)); + + return cont; + } +} diff --git a/pkgs/swift2objc/lib/src/transformer/transform.dart b/pkgs/swift2objc/lib/src/transformer/transform.dart index 378b60383..2e1373dcf 100644 --- a/pkgs/swift2objc/lib/src/transformer/transform.dart +++ b/pkgs/swift2objc/lib/src/transformer/transform.dart @@ -8,20 +8,29 @@ import '../ast/_core/interfaces/nestable_declaration.dart'; import '../ast/declarations/compounds/class_declaration.dart'; import '../ast/declarations/compounds/struct_declaration.dart'; import '../ast/declarations/globals/globals.dart'; +import '_core/dependencies.dart'; import '_core/unique_namer.dart'; import 'transformers/transform_compound.dart'; import 'transformers/transform_globals.dart'; typedef TransformationMap = Map; - /// Transforms the given declarations into the desired ObjC wrapped declarations -List transform(List declarations, { - bool Function(Declaration)? filter -}) { +List transform(List declarations, + {bool Function(Declaration)? filter}) { final transformationMap = {}; - final _declarations = declarations.where(filter ?? (declaration) => true); + final _declarations = + declarations.where(filter ?? (declaration) => true).toList(); + final dependencyVisitor = DependencyVisitor(); + final dependencies = _declarations.fold>( + {}, + (previous, element) => + previous.union(dependencyVisitor.visitDeclaration(element))); + + final _dependentDeclarations = + declarations.where((d) => dependencies.contains(d.name)); + _declarations.addAll(_dependentDeclarations); final globalNamer = UniqueNamer( _declarations.map((declaration) => declaration.name), diff --git a/pkgs/swift2objc/test/unit/filter_test.dart b/pkgs/swift2objc/test/unit/filter_test.dart index b43e5f8c6..f3d9d5ad1 100644 --- a/pkgs/swift2objc/test/unit/filter_test.dart +++ b/pkgs/swift2objc/test/unit/filter_test.dart @@ -9,102 +9,111 @@ import 'package:swift2objc/src/ast/declarations/compounds/class_declaration.dart import 'package:swift2objc/swift2objc.dart'; import 'package:test/test.dart'; +import '../utils/utils.dart'; + void main() { group('Unit test for filter', () { final thisDir = path.join(Directory.current.path, 'test/unit'); final file = path.join(thisDir, 'filter_test_input.swift'); - test('A: Specific Files', () async { - final output = path.join(thisDir, 'filter_test_output_a.swift'); - final actualOutputFile = path.join(thisDir, '${ - path.basenameWithoutExtension(output)}.test${path.extension(output) - }'); - - await generateWrapper(Config( - input: FilesInputConfig( - files: [Uri.file(file)], - ), - outputFile: Uri.file(actualOutputFile), - tempDir: Directory(thisDir).uri, - preamble: '// Test preamble text', - include: (declaration) => declaration.name == 'Engine', - )); - - final actualOutput = await File(actualOutputFile).readAsString(); - final expectedOutput = File(output).readAsStringSync(); - - expect(actualOutput, expectedOutput); - }); - - test('B: Declarations of a specific type', () async { - final output = path.join(thisDir, 'filter_test_output_b.swift'); - final actualOutputFile = path.join(thisDir, '${ - path.basenameWithoutExtension(output)}.test${path.extension(output) - }'); - - await generateWrapper(Config( - input: FilesInputConfig( - files: [Uri.file(file)], - ), - outputFile: Uri.file(actualOutputFile), - tempDir: Directory(thisDir).uri, - preamble: '// Test preamble text', - include: (declaration) => declaration is ClassDeclaration, - )); - - final actualOutput = await File(actualOutputFile).readAsString(); - final expectedOutput = File(output).readAsStringSync(); - - expect(actualOutput, expectedOutput); - }); - - test('C: Nonexistent declaration', () async { - final output = path.join(thisDir, 'filter_test_output_c.swift'); - final actualOutputFile = path.join(thisDir, '${ - path.basenameWithoutExtension(output)}.test${path.extension(output) - }'); - - await generateWrapper(Config( - input: FilesInputConfig( - files: [Uri.file(file)], - ), - outputFile: Uri.file(actualOutputFile), - tempDir: Directory(thisDir).uri, - preamble: '// Test preamble text', - // The following declaration does not exist, - // so none are produced in output - include: (declaration) => declaration.name == 'Ship', - )); - - final actualOutput = await File(actualOutputFile).readAsString(); - final expectedOutput = File(output).readAsStringSync(); - - expect(actualOutput, expectedOutput); - }); + test('A: Specific Files', () async { + final output = path.join(thisDir, 'filter_test_output_a.swift'); + final actualOutputFile = path.join(thisDir, + '${path.basenameWithoutExtension(output)}.test${path.extension(output)}'); + + await generateWrapper(Config( + input: FilesInputConfig( + files: [Uri.file(file)], + ), + outputFile: Uri.file(actualOutputFile), + tempDir: Directory(thisDir).uri, + preamble: '// Test preamble text', + include: (declaration) => declaration.name == 'Engine', + )); + + final actualOutput = await File(actualOutputFile).readAsString(); + final expectedOutput = File(output).readAsStringSync(); + + expectString(actualOutput, expectedOutput); + }); + + test('B: Declarations of a specific type', () async { + final output = path.join(thisDir, 'filter_test_output_b.swift'); + final actualOutputFile = path.join(thisDir, + '${path.basenameWithoutExtension(output)}.test${path.extension(output)}'); + + await generateWrapper(Config( + input: FilesInputConfig( + files: [Uri.file(file)], + ), + outputFile: Uri.file(actualOutputFile), + tempDir: Directory(thisDir).uri, + preamble: '// Test preamble text', + include: (declaration) => declaration is ClassDeclaration, + )); + + final actualOutput = await File(actualOutputFile).readAsString(); + final expectedOutput = File(output).readAsStringSync(); + + expectString(actualOutput, expectedOutput); + }); + + test('C: Nonexistent declaration', () async { + final output = path.join(thisDir, 'filter_test_output_c.swift'); + final actualOutputFile = path.join(thisDir, + '${path.basenameWithoutExtension(output)}.test${path.extension(output)}'); + + await generateWrapper(Config( + input: FilesInputConfig( + files: [Uri.file(file)], + ), + outputFile: Uri.file(actualOutputFile), + tempDir: Directory(thisDir).uri, + preamble: '// Test preamble text', + // The following declaration does not exist, + // so none are produced in output + include: (declaration) => declaration.name == 'Ship', + )); + + final actualOutput = await File(actualOutputFile).readAsString(); + final expectedOutput = File(output).readAsStringSync(); + + expectString(actualOutput, expectedOutput); + }); tearDown(() { - if (File(path.join(thisDir, 'symbolgraph_module.abi.json')).existsSync()) { + if (File(path.join(thisDir, 'symbolgraph_module.abi.json')) + .existsSync()) { File(path.join(thisDir, 'symbolgraph_module.abi.json')).deleteSync(); } - if (File(path.join(thisDir, 'symbolgraph_module.swiftdoc')).existsSync()) { + if (File(path.join(thisDir, 'symbolgraph_module.swiftdoc')) + .existsSync()) { File(path.join(thisDir, 'symbolgraph_module.swiftdoc')).deleteSync(); } - if (File(path.join(thisDir, 'symbolgraph_module.swiftmodule')).existsSync()) { + if (File(path.join(thisDir, 'symbolgraph_module.swiftmodule')) + .existsSync()) { File(path.join(thisDir, 'symbolgraph_module.swiftmodule')).deleteSync(); } - if (File(path.join(thisDir, 'symbolgraph_module.swiftsource')).existsSync()) { + if (File(path.join(thisDir, 'symbolgraph_module.swiftsource')) + .existsSync()) { File(path.join(thisDir, 'symbolgraph_module.swiftsource')).deleteSync(); } - if (File(path.join(thisDir, 'symbolgraph_module.symbols.json')).existsSync()) { - File(path.join(thisDir, 'symbolgraph_module.symbols.json')).deleteSync(); + if (File(path.join(thisDir, 'symbolgraph_module.symbols.json')) + .existsSync()) { + File(path.join(thisDir, 'symbolgraph_module.symbols.json')) + .deleteSync(); } - if (File(path.join(thisDir, 'symbolgraph_module.swiftsourceinfo')).existsSync()) { - File(path.join(thisDir, 'symbolgraph_module.swiftsourceinfo')).deleteSync(); + if (File(path.join(thisDir, 'symbolgraph_module.swiftsourceinfo')) + .existsSync()) { + File(path.join(thisDir, 'symbolgraph_module.swiftsourceinfo')) + .deleteSync(); } - for (final file in Directory(thisDir).listSync().where((t) => path.extension(t.path, 2) == '.test.swift')) { + for (final file in Directory(thisDir) + .listSync() + .where((t) => path.extension(t.path, 2) == '.test.swift')) { if (file is File) file.deleteSync(); } }); }); -} \ No newline at end of file +} diff --git a/pkgs/swift2objc/test/unit/filter_test_output_a.swift b/pkgs/swift2objc/test/unit/filter_test_output_a.swift index 971cabeab..55f24df43 100644 --- a/pkgs/swift2objc/test/unit/filter_test_output_a.swift +++ b/pkgs/swift2objc/test/unit/filter_test_output_a.swift @@ -4,28 +4,30 @@ import Foundation @objc public class EngineWrapper: NSObject { var wrappedInstance: Engine - + @objc public var horsepower: Int { get { wrappedInstance.horsepower } } - + @objc public var type: String { get { wrappedInstance.type } } - + init(_ wrappedInstance: Engine) { self.wrappedInstance = wrappedInstance } - + @objc init(type: String, horsepower: Int) { wrappedInstance = Engine(type: type, horsepower: horsepower) } - + @objc public func displaySpecs() { - wrappedInstance.displaySpecs() + return wrappedInstance.displaySpecs() } + } + diff --git a/pkgs/swift2objc/test/unit/filter_test_output_b.swift b/pkgs/swift2objc/test/unit/filter_test_output_b.swift index 38f13419a..fb0b2039f 100644 --- a/pkgs/swift2objc/test/unit/filter_test_output_b.swift +++ b/pkgs/swift2objc/test/unit/filter_test_output_b.swift @@ -2,9 +2,44 @@ import Foundation +@objc public class DimensionsWrapper: NSObject { + var wrappedInstance: Dimensions + + @objc public var width: Double { + get { + wrappedInstance.width + } + } + + @objc public var height: Double { + get { + wrappedInstance.height + } + } + + @objc public var length: Double { + get { + wrappedInstance.length + } + } + + init(_ wrappedInstance: Dimensions) { + self.wrappedInstance = wrappedInstance + } + + @objc init(length: Double, width: Double, height: Double) { + wrappedInstance = Dimensions(length: length, width: width, height: height) + } + + @objc public func displayDimensions() { + return wrappedInstance.displayDimensions() + } + +} + @objc public class ElectricCarWrapper: NSObject { var wrappedInstance: ElectricCar - + @objc public var batteryCapacity: Int { get { wrappedInstance.batteryCapacity @@ -13,23 +48,20 @@ import Foundation wrappedInstance.batteryCapacity = newValue } } - + init(_ wrappedInstance: ElectricCar) { self.wrappedInstance = wrappedInstance } - - @objc init(make: String, model: String, dimensions: DimensionsWrapper, numberOfDoors: Int, tires: TireWrapper, batteryCapacity: Int) { - wrappedInstance = ElectricCar(make: make, model: model, dimensions: dimensions.wrappedInstance, numberOfDoors: numberOfDoors, tires: tires.wrappedInstance, batteryCapacity: batteryCapacity) - } - + @objc public func chargeBattery() { - wrappedInstance.chargeBattery() + return wrappedInstance.chargeBattery() } + } @objc public class CarWrapper: NSObject { var wrappedInstance: Car - + @objc public var numberOfDoors: Int { get { wrappedInstance.numberOfDoors @@ -38,52 +70,70 @@ import Foundation wrappedInstance.numberOfDoors = newValue } } - - @objc public var tires: TireWrapper { + + init(_ wrappedInstance: Car) { + self.wrappedInstance = wrappedInstance + } + + @objc public func honk() { + return wrappedInstance.honk() + } + +} + +@objc public class EngineWrapper: NSObject { + var wrappedInstance: Engine + + @objc public var horsepower: Int { get { - TireWrapper(wrappedInstance.tires) + wrappedInstance.horsepower } - set { - wrappedInstance.tires = newValue.wrappedInstance + } + + @objc public var type: String { + get { + wrappedInstance.type } } - - init(_ wrappedInstance: Car) { + + init(_ wrappedInstance: Engine) { self.wrappedInstance = wrappedInstance } - - @objc init(make: String, model: String, engine: EngineWrapper, dimensions: DimensionsWrapper, numberOfDoors: Int, tires: TireWrapper) { - wrappedInstance = Car(make: make, model: model, engine: engine.wrappedInstance, dimensions: dimensions.wrappedInstance, numberOfDoors: numberOfDoors, tires: tires.wrappedInstance) + + @objc init(type: String, horsepower: Int) { + wrappedInstance = Engine(type: type, horsepower: horsepower) } - - @objc public func honk() { - wrappedInstance.honk() + + @objc public func displaySpecs() { + return wrappedInstance.displaySpecs() } + } @objc public class GarageWrapper: NSObject { var wrappedInstance: Garage - + init(_ wrappedInstance: Garage) { self.wrappedInstance = wrappedInstance } - + @objc override init() { wrappedInstance = Garage() } - - @objc public func addVehicle(vehicle: VehicleWrapper) { - wrappedInstance.addVehicle(vehicle: vehicle.wrappedInstance) + + @objc public func addVehicle(_ vehicle: VehicleWrapper) { + return wrappedInstance.addVehicle(vehicle.wrappedInstance) } - + @objc public func listVehicles() { - wrappedInstance.listVehicles() + return wrappedInstance.listVehicles() } + } @objc public class BicycleWrapper: NSObject { var wrappedInstance: Bicycle - + @objc public var dimensions: DimensionsWrapper { get { DimensionsWrapper(wrappedInstance.dimensions) @@ -92,7 +142,7 @@ import Foundation wrappedInstance.dimensions = newValue.wrappedInstance } } - + @objc public var brand: String { get { wrappedInstance.brand @@ -101,7 +151,7 @@ import Foundation wrappedInstance.brand = newValue } } - + @objc public var gearCount: Int { get { wrappedInstance.gearCount @@ -110,23 +160,77 @@ import Foundation wrappedInstance.gearCount = newValue } } - + init(_ wrappedInstance: Bicycle) { self.wrappedInstance = wrappedInstance } - + @objc init(brand: String, gearCount: Int, dimensions: DimensionsWrapper) { wrappedInstance = Bicycle(brand: brand, gearCount: gearCount, dimensions: dimensions.wrappedInstance) } - + @objc public func pedal() { - wrappedInstance.pedal() + return wrappedInstance.pedal() + } + +} + +@objc public class VehicleWrapper: NSObject { + var wrappedInstance: Vehicle + + @objc public var dimensions: DimensionsWrapper { + get { + DimensionsWrapper(wrappedInstance.dimensions) + } + set { + wrappedInstance.dimensions = newValue.wrappedInstance + } + } + + @objc public var make: String { + get { + wrappedInstance.make + } + set { + wrappedInstance.make = newValue + } + } + + @objc public var model: String { + get { + wrappedInstance.model + } + set { + wrappedInstance.model = newValue + } } + + @objc public var engine: EngineWrapper { + get { + EngineWrapper(wrappedInstance.engine) + } + set { + wrappedInstance.engine = newValue.wrappedInstance + } + } + + init(_ wrappedInstance: Vehicle) { + self.wrappedInstance = wrappedInstance + } + + @objc init(make: String, model: String, engine: EngineWrapper, dimensions: DimensionsWrapper) { + wrappedInstance = Vehicle(make: make, model: model, engine: engine.wrappedInstance, dimensions: dimensions.wrappedInstance) + } + + @objc public func displayInfo() { + return wrappedInstance.displayInfo() + } + } @objc public class VehicleWrapper: NSObject { var wrappedInstance: Vehicle - + @objc public var dimensions: DimensionsWrapper { get { DimensionsWrapper(wrappedInstance.dimensions) @@ -135,7 +239,7 @@ import Foundation wrappedInstance.dimensions = newValue.wrappedInstance } } - + @objc public var make: String { get { wrappedInstance.make @@ -144,7 +248,7 @@ import Foundation wrappedInstance.make = newValue } } - + @objc public var model: String { get { wrappedInstance.model @@ -153,7 +257,7 @@ import Foundation wrappedInstance.model = newValue } } - + @objc public var engine: EngineWrapper { get { EngineWrapper(wrappedInstance.engine) @@ -162,16 +266,18 @@ import Foundation wrappedInstance.engine = newValue.wrappedInstance } } - + init(_ wrappedInstance: Vehicle) { self.wrappedInstance = wrappedInstance } - + @objc init(make: String, model: String, engine: EngineWrapper, dimensions: DimensionsWrapper) { wrappedInstance = Vehicle(make: make, model: model, engine: engine.wrappedInstance, dimensions: dimensions.wrappedInstance) } - + @objc public func displayInfo() { - wrappedInstance.displayInfo() + return wrappedInstance.displayInfo() } + } + diff --git a/pkgs/swift2objc/test/unit/filter_test_output_c.swift b/pkgs/swift2objc/test/unit/filter_test_output_c.swift index 53226acbd..fd38b9f0e 100644 --- a/pkgs/swift2objc/test/unit/filter_test_output_c.swift +++ b/pkgs/swift2objc/test/unit/filter_test_output_c.swift @@ -1,3 +1,4 @@ // Test preamble text import Foundation + diff --git a/pkgs/swift2objc/test/utils/utils.dart b/pkgs/swift2objc/test/utils/utils.dart new file mode 100644 index 000000000..e184bd6d1 --- /dev/null +++ b/pkgs/swift2objc/test/utils/utils.dart @@ -0,0 +1,9 @@ +import 'package:test/test.dart'; + +void expectString(String a, String b) { + final trimmedA = a.replaceAll(RegExp(r'\s+'), ''); + final trimmedB = b.replaceAll(RegExp(r'\s+'), ''); + ; + + expect(trimmedA, trimmedB); +} From 38bbbbcf00be9ed0bab5f809515e07ff6e3f271f Mon Sep 17 00:00:00 2001 From: --add Date: Mon, 2 Dec 2024 01:10:53 -0600 Subject: [PATCH 6/7] minor fixes and added deep transitive support --- .../lib/src/transformer/transform.dart | 36 +++++++++---- .../test/unit/filter_test_output_b.swift | 53 ------------------- 2 files changed, 26 insertions(+), 63 deletions(-) diff --git a/pkgs/swift2objc/lib/src/transformer/transform.dart b/pkgs/swift2objc/lib/src/transformer/transform.dart index 2e1373dcf..00cecf650 100644 --- a/pkgs/swift2objc/lib/src/transformer/transform.dart +++ b/pkgs/swift2objc/lib/src/transformer/transform.dart @@ -15,22 +15,38 @@ import 'transformers/transform_globals.dart'; typedef TransformationMap = Map; +Set generateDependencies(Iterable decls, {Iterable? allDecls}) { + final dependencies = {}; + final dependencyVisitor = DependencyVisitor(); + + var _d = decls; + + while (true) { + final deps = _d.fold>( + {}, + (previous, element) => + previous.union(dependencyVisitor.visitDeclaration(element))); + final depDecls = + (allDecls ?? decls).where((d) => deps.contains(d.name)); + if (depDecls.isEmpty || (dependencies.union(depDecls.toSet()).length) == dependencies.length) { + break; + } else { + dependencies.addAll(depDecls); + _d = depDecls; + } + } + + return dependencies; +} + /// Transforms the given declarations into the desired ObjC wrapped declarations List transform(List declarations, {bool Function(Declaration)? filter}) { final transformationMap = {}; final _declarations = - declarations.where(filter ?? (declaration) => true).toList(); - final dependencyVisitor = DependencyVisitor(); - final dependencies = _declarations.fold>( - {}, - (previous, element) => - previous.union(dependencyVisitor.visitDeclaration(element))); - - final _dependentDeclarations = - declarations.where((d) => dependencies.contains(d.name)); - _declarations.addAll(_dependentDeclarations); + declarations.where(filter ?? (declaration) => true).toSet(); + _declarations.addAll(generateDependencies(_declarations, allDecls: declarations)); final globalNamer = UniqueNamer( _declarations.map((declaration) => declaration.name), diff --git a/pkgs/swift2objc/test/unit/filter_test_output_b.swift b/pkgs/swift2objc/test/unit/filter_test_output_b.swift index fb0b2039f..1d0251927 100644 --- a/pkgs/swift2objc/test/unit/filter_test_output_b.swift +++ b/pkgs/swift2objc/test/unit/filter_test_output_b.swift @@ -228,56 +228,3 @@ import Foundation } -@objc public class VehicleWrapper: NSObject { - var wrappedInstance: Vehicle - - @objc public var dimensions: DimensionsWrapper { - get { - DimensionsWrapper(wrappedInstance.dimensions) - } - set { - wrappedInstance.dimensions = newValue.wrappedInstance - } - } - - @objc public var make: String { - get { - wrappedInstance.make - } - set { - wrappedInstance.make = newValue - } - } - - @objc public var model: String { - get { - wrappedInstance.model - } - set { - wrappedInstance.model = newValue - } - } - - @objc public var engine: EngineWrapper { - get { - EngineWrapper(wrappedInstance.engine) - } - set { - wrappedInstance.engine = newValue.wrappedInstance - } - } - - init(_ wrappedInstance: Vehicle) { - self.wrappedInstance = wrappedInstance - } - - @objc init(make: String, model: String, engine: EngineWrapper, dimensions: DimensionsWrapper) { - wrappedInstance = Vehicle(make: make, model: model, engine: engine.wrappedInstance, dimensions: dimensions.wrappedInstance) - } - - @objc public func displayInfo() { - return wrappedInstance.displayInfo() - } - -} - From 19e1504c60334f30bba971bfc8bc8a0045cef3bc Mon Sep 17 00:00:00 2001 From: --add Date: Mon, 2 Dec 2024 09:12:18 -0600 Subject: [PATCH 7/7] removed dangling semicolon --- pkgs/swift2objc/test/utils/utils.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/pkgs/swift2objc/test/utils/utils.dart b/pkgs/swift2objc/test/utils/utils.dart index e184bd6d1..27ea25ad4 100644 --- a/pkgs/swift2objc/test/utils/utils.dart +++ b/pkgs/swift2objc/test/utils/utils.dart @@ -3,7 +3,6 @@ import 'package:test/test.dart'; void expectString(String a, String b) { final trimmedA = a.replaceAll(RegExp(r'\s+'), ''); final trimmedB = b.replaceAll(RegExp(r'\s+'), ''); - ; expect(trimmedA, trimmedB); }