Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[swift2objc] Filtering Support #1730

Merged
merged 11 commits into from
Dec 16, 2024
12 changes: 12 additions & 0 deletions pkgs/swift2objc/lib/src/config.dart
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -32,11 +34,21 @@ class Config {
/// intermediate files after generating the wrapper
final Uri? tempDir;

/// Filter function to filter APIs
///
/// APIs can be filtered by name
///
/// Includes all declarations by default
final bool Function(Declaration declaration)? include;
nikeokoronkwo marked this conversation as resolved.
Show resolved Hide resolved

static bool _defaultInclude(_) => true;

const Config({
required this.input,
required this.outputFile,
this.tempDir,
this.preamble,
this.include = Config._defaultInclude
});
}

Expand Down
5 changes: 3 additions & 2 deletions pkgs/swift2objc/lib/src/generate_wrapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ Future<void> 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,
Expand Down
16 changes: 11 additions & 5 deletions pkgs/swift2objc/lib/src/transformer/transform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,26 @@ import 'transformers/transform_globals.dart';

typedef TransformationMap = Map<Declaration, Declaration>;

List<Declaration> transform(List<Declaration> declarations) {
/// Transforms the given declarations into the desired ObjC wrapped declarations
List<Declaration> transform(List<Declaration> declarations, {
bool Function(Declaration)? filter
}) {
final TransformationMap transformationMap;
final _filter = filter ?? (declaration) => true;

final _declarations = declarations.where((d) => _filter(d));
nikeokoronkwo marked this conversation as resolved.
Show resolved Hide resolved

transformationMap = {};

final globalNamer = UniqueNamer(
declarations.map((declaration) => declaration.name),
_declarations.map((declaration) => declaration.name),
);

final globals = Globals(
functions: declarations.whereType<GlobalFunctionDeclaration>().toList(),
variables: declarations.whereType<GlobalVariableDeclaration>().toList(),
functions: _declarations.whereType<GlobalFunctionDeclaration>().toList(),
variables: _declarations.whereType<GlobalVariableDeclaration>().toList(),
);
final nonGlobals = declarations
final nonGlobals = _declarations
.where(
(declaration) =>
declaration is! GlobalFunctionDeclaration &&
Expand Down
110 changes: 110 additions & 0 deletions pkgs/swift2objc/test/unit/filter_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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/src/ast/declarations/compounds/class_declaration.dart';
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');
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);
});

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();
}

for (final file in Directory(thisDir).listSync().where((t) => path.extension(t.path, 2) == '.test.swift')) {
if (file is File) file.deleteSync();
}
});
});
}
135 changes: 135 additions & 0 deletions pkgs/swift2objc/test/unit/filter_test_input.swift
Original file line number Diff line number Diff line change
@@ -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)")
}
}
}
31 changes: 31 additions & 0 deletions pkgs/swift2objc/test/unit/filter_test_output_a.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
Loading
Loading