From f2a0e28dca61bb37fac6b352db9ade98f45deb6b Mon Sep 17 00:00:00 2001 From: nikeokoronkwo <150845642+nikeokoronkwo@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:22:48 -0500 Subject: [PATCH] [swift2objc] Filtering Support (#1730) --- pkgs/swift2objc/lib/src/config.dart | 23 +- pkgs/swift2objc/lib/src/generate_wrapper.dart | 4 +- .../parser/parsers/parse_declarations.dart | 1 + .../src/transformer/_core/dependencies.dart | 259 ++++++++++++++++++ .../lib/src/transformer/transform.dart | 26 +- pkgs/swift2objc/test/unit/filter_test.dart | 116 ++++++++ .../test/unit/filter_test_input.swift | 135 +++++++++ .../test/unit/filter_test_output_a.swift | 33 +++ .../test/unit/filter_test_output_b.swift | 230 ++++++++++++++++ .../test/unit/filter_test_output_c.swift | 4 + pkgs/swift2objc/test/utils/utils.dart | 8 + 11 files changed, 826 insertions(+), 13 deletions(-) create mode 100644 pkgs/swift2objc/lib/src/transformer/_core/dependencies.dart 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_a.swift create mode 100644 pkgs/swift2objc/test/unit/filter_test_output_b.swift create mode 100644 pkgs/swift2objc/test/unit/filter_test_output_c.swift 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 2f05353c2..30847e745 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,12 +34,21 @@ class Config { /// intermediate files after generating the wrapper. final Uri? tempDir; - const Config({ - required this.input, - required this.outputFile, - this.tempDir, - this.preamble, - }); + /// 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}); } /// 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 4ae8c7e51..bfe84aa70 100644 --- a/pkgs/swift2objc/lib/src/generate_wrapper.dart +++ b/pkgs/swift2objc/lib/src/generate_wrapper.dart @@ -46,8 +46,8 @@ Future generateWrapper(Config config) async { }; final declarations = parseAst(symbolgraphJson); - final transformedDeclarations = transform(declarations); - + final transformedDeclarations = + transform(declarations, filter: config.include); final wrapperCode = generate( transformedDeclarations, moduleName: sourceModule, diff --git a/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart b/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart index 5036bba8d..36b74c4f4 100644 --- a/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart +++ b/pkgs/swift2objc/lib/src/parser/parsers/parse_declarations.dart @@ -25,6 +25,7 @@ List parseDeclarations(ParsedSymbolgraph symbolgraph) { return declarations.topLevelOnly; } +// TODO(https://github.com/dart-lang/native/issues/1815): Support for extensions Declaration parseDeclaration( ParsedSymbol parsedSymbol, ParsedSymbolgraph symbolgraph, 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..30e693d88 --- /dev/null +++ b/pkgs/swift2objc/lib/src/transformer/_core/dependencies.dart @@ -0,0 +1,259 @@ +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'; + +// TODO(https://github.com/dart-lang/native/issues/1814): Type restrictions have not yet been implemented in system +class DependencyVisitor { + final Iterable declarations; + Set visitedDeclarations = {}; + + DependencyVisitor(this.declarations); + + Set visit(Declaration dec) { + final dependencies = {}; + + Iterable d = [dec]; + + while (true) { + final deps = d.fold>( + {}, (previous, element) => previous.union(visitDeclaration(element))); + final depDecls = declarations.where((d) => deps.contains(d.id)); + if (depDecls.isEmpty || + (dependencies.union(depDecls.toSet()).length) == + dependencies.length) { + break; + } else { + dependencies.addAll(depDecls); + d = depDecls; + } + } + + visitedDeclarations.addAll(dependencies); + + return dependencies; + } + + 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 ??= {}; + + // visit nested declarations + for (var n in decl.nestedDeclarations) { + visitDeclaration(n, cont); + } + + // visit protocols + for (var p in decl.conformedProtocols) { + 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 + for (var d in decl.properties) { + visitVariable(d, cont); + } + + // visit methods + for (var m in decl.methods) { + visitFunction(m, cont); + } + + // visit initializers + for (var i in decl.initializers) { + visitInitializer(i, cont); + } + + // visit nested declarations + for (var n in decl.nestedDeclarations) { + visitDeclaration(n, cont); + } + + // visit protocols + for (var p in decl.conformedProtocols) { + 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 + for (var d in decl.properties) { + visitVariable(d, cont); + } + + // visit methods + for (var m in decl.methods) { + visitFunction(m, cont); + } + + // visit initializers + for (var i in decl.initializers) { + visitInitializer(i, cont); + } + + // visit super if any + if (decl.superClass != null) { + visitDeclaration(decl.superClass!.declaration, cont); + } + + // visit nested declarations + for (var n in decl.nestedDeclarations) { + visitDeclaration(n, cont); + } + + // visit protocols + for (var p in decl.conformedProtocols) { + 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 + for (var d in decl.properties) { + visitVariable(d, cont); + } + + // visit methods + for (var m in decl.methods) { + visitFunction(m, cont); + } + + // visit initializers + for (var i in decl.initializers) { + visitInitializer(i, cont); + } + + // visit nested declarations + for (var n in decl.nestedDeclarations) { + visitDeclaration(n, cont); + } + + // visit protocols + for (var p in decl.conformedProtocols) { + 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 + for (var p in decl.params) { + visitParameter(p, cont); + } + + return cont; + } + + Set visitFunction(FunctionDeclaration decl, [Set? context]) { + final cont = context ??= {}; + + // visit parameters + for (var p in decl.params) { + 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); + + 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 + // check what kind of type [type] is + switch (type) { + case DeclaredType(): + cont.add(type.id); + break; + case GenericType(): + // do nothing + break; + case OptionalType(): + visitType(type.child, cont); + } + return cont; + } +} diff --git a/pkgs/swift2objc/lib/src/transformer/transform.dart b/pkgs/swift2objc/lib/src/transformer/transform.dart index 4322dd366..750d2efcd 100644 --- a/pkgs/swift2objc/lib/src/transformer/transform.dart +++ b/pkgs/swift2objc/lib/src/transformer/transform.dart @@ -8,24 +8,40 @@ 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; -List transform(List declarations) { +Set generateDependencies( + Iterable decls, Iterable allDecls) { + final visitor = DependencyVisitor(allDecls); + for (final dec in decls) { + visitor.visit(dec); + } + + return visitor.visitedDeclarations; +} + +/// Transforms the given declarations into the desired ObjC wrapped declarations +List transform(List declarations, + {required bool Function(Declaration) filter}) { final transformationMap = {}; + final declarations0 = declarations.where(filter).toSet(); + declarations0.addAll(generateDependencies(declarations0, declarations)); + final globalNamer = UniqueNamer( - declarations.map((declaration) => declaration.name), + declarations0.map((declaration) => declaration.name), ); final globals = Globals( - functions: declarations.whereType().toList(), - variables: declarations.whereType().toList(), + functions: declarations0.whereType().toList(), + variables: declarations0.whereType().toList(), ); - final nonGlobals = declarations + final nonGlobals = declarations0 .where( (declaration) => declaration is! GlobalFunctionDeclaration && diff --git a/pkgs/swift2objc/test/unit/filter_test.dart b/pkgs/swift2objc/test/unit/filter_test.dart new file mode 100644 index 000000000..3b7828c14 --- /dev/null +++ b/pkgs/swift2objc/test/unit/filter_test.dart @@ -0,0 +1,116 @@ +// 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 p; +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 = p.join(Directory.current.path, 'test/unit'); + + final file = p.join(thisDir, 'filter_test_input.swift'); + test('A: Specific Files', () async { + final output = p.join(thisDir, 'filter_test_output_a.swift'); + final actualOutputFile = p.join(thisDir, + '${p.basenameWithoutExtension(output)}.test${p.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 = p.join(thisDir, 'filter_test_output_b.swift'); + final actualOutputFile = p.join(thisDir, + '${p.basenameWithoutExtension(output)}.test${p.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 = p.join(thisDir, 'filter_test_output_c.swift'); + final actualOutputFile = p.join(thisDir, + '${p.basenameWithoutExtension(output)}.test${p.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(p.join(thisDir, 'symbolgraph_module.abi.json')).existsSync()) { + File(p.join(thisDir, 'symbolgraph_module.abi.json')).deleteSync(); + } + if (File(p.join(thisDir, 'symbolgraph_module.swiftdoc')).existsSync()) { + File(p.join(thisDir, 'symbolgraph_module.swiftdoc')).deleteSync(); + } + if (File(p.join(thisDir, 'symbolgraph_module.swiftmodule')) + .existsSync()) { + File(p.join(thisDir, 'symbolgraph_module.swiftmodule')).deleteSync(); + } + if (File(p.join(thisDir, 'symbolgraph_module.swiftsource')) + .existsSync()) { + File(p.join(thisDir, 'symbolgraph_module.swiftsource')).deleteSync(); + } + if (File(p.join(thisDir, 'symbolgraph_module.symbols.json')) + .existsSync()) { + File(p.join(thisDir, 'symbolgraph_module.symbols.json')).deleteSync(); + } + if (File(p.join(thisDir, 'symbolgraph_module.swiftsourceinfo')) + .existsSync()) { + File(p.join(thisDir, 'symbolgraph_module.swiftsourceinfo')) + .deleteSync(); + } + + for (final file in Directory(thisDir) + .listSync() + .where((t) => p.extension(t.path, 2) == '.test.swift')) { + if (file is File) file.deleteSync(); + } + }); + }); +} 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..40992e06a --- /dev/null +++ b/pkgs/swift2objc/test/unit/filter_test_input.swift @@ -0,0 +1,135 @@ +import Foundation + +public struct Engine { + public let type: String + public let horsepower: Int + + 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 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..55f24df43 --- /dev/null +++ b/pkgs/swift2objc/test/unit/filter_test_output_a.swift @@ -0,0 +1,33 @@ +// 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() { + 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 new file mode 100644 index 000000000..1d0251927 --- /dev/null +++ b/pkgs/swift2objc/test/unit/filter_test_output_b.swift @@ -0,0 +1,230 @@ +// Test preamble text + +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 + } + set { + wrappedInstance.batteryCapacity = newValue + } + } + + init(_ wrappedInstance: ElectricCar) { + self.wrappedInstance = wrappedInstance + } + + @objc public func chargeBattery() { + return wrappedInstance.chargeBattery() + } + +} + +@objc public class CarWrapper: NSObject { + var wrappedInstance: Car + + @objc public var numberOfDoors: Int { + get { + wrappedInstance.numberOfDoors + } + set { + wrappedInstance.numberOfDoors = newValue + } + } + + 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 { + 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() { + 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) { + return wrappedInstance.addVehicle(vehicle.wrappedInstance) + } + + @objc public func listVehicles() { + return 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() { + 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() + } + +} + diff --git a/pkgs/swift2objc/test/unit/filter_test_output_c.swift b/pkgs/swift2objc/test/unit/filter_test_output_c.swift new file mode 100644 index 000000000..fd38b9f0e --- /dev/null +++ b/pkgs/swift2objc/test/unit/filter_test_output_c.swift @@ -0,0 +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..27ea25ad4 --- /dev/null +++ b/pkgs/swift2objc/test/utils/utils.dart @@ -0,0 +1,8 @@ +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); +}