purgeStaleCppKotlinRegistrations function
Removes stale <Module>JniBridge.register(...) calls from Plugin.kt for
modules that have been converted to NativeImpl.cpp.
When a user switches android: NativeImpl.kotlin → AndroidNativeImpl.cpp
(or any C++ variant), the old registration call is left as dead code that
causes a Kotlin "Unresolved reference" compile error. This function finds
and removes those stale calls automatically on every nitrogen link run.
Implementation
void purgeStaleCppKotlinRegistrations(
List<ModuleInfo> cppModules, {
String baseDir = '.',
}) {
if (cppModules.isEmpty) return;
final kotlinDir = Directory(
p.join(baseDir, 'android', 'src', 'main', 'kotlin'),
);
if (!kotlinDir.existsSync()) return;
final pluginFiles = kotlinDir
.listSync(recursive: true, followLinks: false)
.whereType<File>()
.where((f) => !f.path.contains('.symlinks'))
.where((f) => f.path.endsWith('Plugin.kt'))
.toList();
if (pluginFiles.isEmpty) return;
final pluginFile = pluginFiles.first;
var content = pluginFile.readAsStringSync();
bool modified = false;
for (final m in cppModules) {
// Match: <Module>JniBridge.register(<anything>) OR .registerFactory(...)
// Anchored to line start (^ with multiLine): without it, a module whose
// class name is a SUFFIX of another's (e.g. cpp module `Present` vs
// Kotlin module `WebgpuPresent`) would match inside
// `WebgpuPresentJniBridge.register(...)` and corrupt that line.
final stalePattern = RegExp(
r'^[ \t]*' + RegExp.escape('${m.module}JniBridge') + r'\.register\w*\(.*\)[ \t]*\r?\n?',
multiLine: true,
);
if (stalePattern.hasMatch(content)) {
content = content.replaceAll(stalePattern, '');
modified = true;
}
}
// Clean up orphaned imports for the removed JniBridge class — ONLY when
// nothing else in the file still references it. An all-cpp Android module
// still emits a JniBridge class (lifecycle hooks such as
// onActivityAttached), and a user's ActivityAware plugin legitimately
// calls it; removing a still-used import breaks compileDebugKotlin with
// "Unresolved reference" (issue #16, reopened).
for (final m in cppModules) {
// `\.` before the class and a non-identifier boundary after it, so the
// import of a longer-named sibling (WebgpuPresentJniBridge when purging
// Present) is never mistaken for this module's import.
final importPattern = RegExp(
r'^import [^\n]+?\.' + RegExp.escape('${m.module}JniBridge') + r'(?![A-Za-z0-9_])[^\n]*\n?',
multiLine: true,
);
if (!importPattern.hasMatch(content)) continue;
final withoutImports = content.replaceAll(importPattern, '');
// Identifier-boundary usage check: `PresentJniBridge` inside
// `WebgpuPresentJniBridge` must not count as a remaining usage.
final usagePattern = RegExp(
r'(?<![A-Za-z0-9_.])' + RegExp.escape('${m.module}JniBridge') + r'(?![A-Za-z0-9_])',
);
if (!usagePattern.hasMatch(withoutImports)) {
content = withoutImports;
modified = true;
}
}
if (modified) pluginFile.writeAsStringSync(content);
}