• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dart-lang / ffigen / 5305802354

18 Jun 2023 09:32PM UTC coverage: 89.404% (-2.5%) from 91.879%
5305802354

Pull #586

github

web-flow
Merge f948c38e1 into 9030eb512
Pull Request #586: Add ffigen schema and refer them in example config yaml using modeline

624 of 624 new or added lines in 3 files covered. (100.0%)

3451 of 3860 relevant lines covered (89.4%)

27.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.62
/lib/src/config_provider/spec_utils.dart
1
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2
// for details. All rights reserved. Use of this source code is governed by a
3
// BSD-style license that can be found in the LICENSE file.
4

5
import 'dart:io';
6

7
import 'package:ffigen/src/code_generator.dart';
8
import 'package:ffigen/src/code_generator/utils.dart';
9
import 'package:ffigen/src/header_parser/type_extractor/cxtypekindmap.dart';
10
import 'package:file/local.dart';
11
import 'package:glob/glob.dart';
12
import 'package:logging/logging.dart';
13
import 'package:package_config/package_config.dart';
14
import 'package:path/path.dart' as p;
15
import 'package:quiver/pattern.dart' as quiver;
16
import 'package:yaml/yaml.dart';
17

18
import '../strings.dart' as strings;
19
import 'config_types.dart';
20

21
final _logger = Logger('ffigen.config_provider.spec_utils');
105✔
22

23
/// Replaces the path separators according to current platform.
24
String _replaceSeparators(String path) {
50✔
25
  if (Platform.isWindows) {
50✔
26
    return path.replaceAll(p.posix.separator, p.windows.separator);
×
27
  } else {
28
    return path.replaceAll(p.windows.separator, p.posix.separator);
250✔
29
  }
30
}
31

32
/// Replaces the path separators according to current platform. If a relative
33
/// path is passed in, it is resolved relative to the config path, and the
34
/// absolute path is returned.
35
String _normalizePath(String path, String? configFilename) {
50✔
36
  final skipNormalization =
37
      (configFilename == null) || p.isAbsolute(path) || path.startsWith("**");
16✔
38
  return _replaceSeparators(
50✔
39
      skipNormalization ? path : p.join(p.dirname(configFilename), path));
16✔
40
}
41

42
/// Checks if type of value is [T], logs an error if it's not.
43
///
44
/// [key] is printed as `'item1 -> item2 => item3'` in log message.
45
bool checkType<T>(List<String> keys, dynamic value) {
×
46
  if (value is! T) {
×
47
    _logger.severe(
×
48
        "Expected value of key '${keys.join(' -> ')}' to be of type '$T'.");
×
49
    return false;
50
  }
51
  return true;
52
}
53

54
/// Checks if there are nested [key] in [map].
55
bool checkKeyInYaml(List<String> key, YamlMap map) {
×
56
  dynamic last = map;
57
  for (final k in key) {
×
58
    if (last is YamlMap) {
×
59
      if (!last.containsKey(k)) return false;
×
60
      last = last[k];
×
61
    } else {
62
      return false;
63
    }
64
  }
65
  // The entry for the last key may be null.
66
  return true;
67
}
68

69
/// Extracts value of nested [key] from [map].
70
dynamic getKeyValueFromYaml(List<String> key, YamlMap map) {
×
71
  if (checkKeyInYaml(key, map)) {
×
72
    dynamic last = map;
73
    for (final k in key) {
×
74
      last = last[k];
×
75
    }
76
    return last;
77
  }
78

79
  return null;
80
}
81

82
/// Recursively checks the keys in [configKeyMap] from [allowedKeyList].
83
void warnUnknownKeys(List<List<String>> allowedKeyList, YamlMap configKeyMap) {
×
84
  final allowedKeyMap = <String, dynamic>{};
×
85
  for (final specKeys in allowedKeyList) {
×
86
    var item = allowedKeyMap;
87
    for (final specSubKey in specKeys) {
×
88
      item.putIfAbsent(specSubKey, () => <String, dynamic>{});
×
89
      item = item[specSubKey] as Map<String, dynamic>;
×
90
    }
91
    // Add empty key to mark that any sub-keys of this key are allowed.
92
    item[''] = <String, dynamic>{};
×
93
  }
94
  _warnUnknownKeysInMap(allowedKeyMap, configKeyMap, <dynamic>[]);
×
95
}
96

97
/// Recursive function to check a key set in a configKeyMap.
98
void _warnUnknownKeysInMap(Map<String, dynamic> allowedKeyMap,
×
99
    dynamic configKeyMap, List<dynamic> prev) {
100
  if (allowedKeyMap.containsKey('') || configKeyMap is! YamlMap) {
×
101
    return;
102
  }
103
  for (final key in configKeyMap.keys) {
×
104
    if (allowedKeyMap.containsKey(key)) {
×
105
      prev.add(key);
×
106
      _warnUnknownKeysInMap(
×
107
          allowedKeyMap[key] as Map<String, dynamic>, configKeyMap[key], prev);
×
108
      prev.removeLast();
×
109
    } else {
110
      prev.add(key);
×
111
      _logger.warning('Unknown key - ${prev.join(' -> ')}.');
×
112
      prev.removeLast();
×
113
    }
114
  }
115
}
116

117
Map<String, LibraryImport> libraryImportsExtractor(
1✔
118
    Map<String, String>? typeMap) {
119
  final resultMap = <String, LibraryImport>{};
1✔
120
  if (typeMap != null) {
121
    for (final typeName in typeMap.keys) {
2✔
122
      resultMap[typeName] =
1✔
123
          LibraryImport(typeName, typeMap[typeName] as String);
2✔
124
    }
125
  }
126
  return resultMap;
127
}
128

129
bool libraryImportsValidator(List<String> name, dynamic yamlConfig) {
×
130
  if (!checkType<YamlMap>(name, yamlConfig)) {
×
131
    return false;
132
  }
133
  for (final key in (yamlConfig as YamlMap).keys) {
×
134
    if (!checkType<String>([...name, key as String], yamlConfig[key])) {
×
135
      return false;
136
    }
137
    if (strings.predefinedLibraryImports.containsKey(key)) {
×
138
      _logger.severe(
×
139
          'library-import -> $key should not collide with any predefined imports - ${strings.predefinedLibraryImports.keys}.');
×
140
      return false;
141
    }
142
  }
143
  return true;
144
}
145

146
void loadImportedTypes(YamlMap fileConfig,
1✔
147
    Map<String, ImportedType> usrTypeMappings, LibraryImport libraryImport) {
148
  final symbols = fileConfig['symbols'] as YamlMap;
1✔
149
  for (final key in symbols.keys) {
2✔
150
    final usr = key as String;
151
    final value = symbols[usr]! as YamlMap;
1✔
152
    usrTypeMappings[usr] = ImportedType(
2✔
153
        libraryImport, value['name'] as String, value['name'] as String);
2✔
154
  }
155
}
156

157
YamlMap loadSymbolFile(String symbolFilePath, String? configFileName,
1✔
158
    PackageConfig? packageConfig) {
159
  final path = symbolFilePath.startsWith('package:')
1✔
160
      ? packageConfig!.resolve(Uri.parse(symbolFilePath))!.toFilePath()
3✔
161
      : _normalizePath(symbolFilePath, configFileName);
×
162

163
  return loadYaml(File(path).readAsStringSync()) as YamlMap;
3✔
164
}
165

166
Map<String, ImportedType> symbolFileImportExtractor(
1✔
167
    List<String> yamlConfig,
168
    Map<String, LibraryImport> libraryImports,
169
    String? configFileName,
170
    PackageConfig? packageConfig) {
171
  final resultMap = <String, ImportedType>{};
1✔
172
  for (final item in yamlConfig) {
2✔
173
    String symbolFilePath;
174
    symbolFilePath = item;
175
    final symbolFile =
176
        loadSymbolFile(symbolFilePath, configFileName, packageConfig);
1✔
177
    final formatVersion = symbolFile[strings.formatVersion] as String;
1✔
178
    if (formatVersion.split('.')[0] !=
3✔
179
        strings.symbolFileFormatVersion.split('.')[0]) {
2✔
180
      _logger.severe(
×
181
          'Incompatible format versions for file $symbolFilePath: ${strings.symbolFileFormatVersion}(ours), $formatVersion(theirs).');
×
182
      exit(1);
×
183
    }
184
    final uniqueNamer = UniqueNamer(libraryImports.keys
2✔
185
        .followedBy([strings.defaultSymbolFileImportPrefix]).toSet());
3✔
186
    for (final file in (symbolFile[strings.files] as YamlMap).keys) {
3✔
187
      final existingImports =
188
          libraryImports.values.where((element) => element.importPath == file);
2✔
189
      if (existingImports.isEmpty) {
1✔
190
        final name =
191
            uniqueNamer.makeUnique(strings.defaultSymbolFileImportPrefix);
1✔
192
        libraryImports[name] = LibraryImport(name, file as String);
2✔
193
      }
194
      final libraryImport = libraryImports.values.firstWhere(
2✔
195
        (element) => element.importPath == file,
3✔
196
      );
197
      loadImportedTypes(
1✔
198
          symbolFile[strings.files][file] as YamlMap, resultMap, libraryImport);
2✔
199
    }
200
  }
201
  return resultMap;
202
}
203

204
Map<String, List<String>> typeMapExtractor(Map<dynamic, dynamic>? yamlConfig) {
3✔
205
  // Key - type_name, Value - [lib, cType, dartType].
206
  final resultMap = <String, List<String>>{};
3✔
207
  final typeMap = yamlConfig;
208
  if (typeMap != null) {
209
    for (final typeName in typeMap.keys) {
6✔
210
      final typeConfigItem = typeMap[typeName] as Map;
3✔
211
      resultMap[typeName as String] = [
6✔
212
        typeConfigItem[strings.lib] as String,
3✔
213
        typeConfigItem[strings.cType] as String,
3✔
214
        typeConfigItem[strings.dartType] as String,
3✔
215
      ];
216
    }
217
  }
218
  return resultMap;
219
}
220

221
Map<String, ImportedType> makeImportTypeMapping(
50✔
222
    Map<String, List<String>> rawTypeMappings,
223
    Map<String, LibraryImport> libraryImportsMap) {
224
  final typeMappings = <String, ImportedType>{};
50✔
225
  for (final key in rawTypeMappings.keys) {
53✔
226
    final lib = rawTypeMappings[key]![0];
6✔
227
    final cType = rawTypeMappings[key]![1];
6✔
228
    final dartType = rawTypeMappings[key]![2];
6✔
229
    if (strings.predefinedLibraryImports.containsKey(lib)) {
6✔
230
      typeMappings[key] =
3✔
231
          ImportedType(strings.predefinedLibraryImports[lib]!, cType, dartType);
9✔
232
    } else if (libraryImportsMap.containsKey(lib)) {
1✔
233
      typeMappings[key] =
1✔
234
          ImportedType(libraryImportsMap[lib]!, cType, dartType);
2✔
235
    } else {
236
      throw Exception("Please declare $lib under library-imports.");
×
237
    }
238
  }
239
  return typeMappings;
240
}
241

242
Type makePointerToType(Type type, int pointerCount) {
1✔
243
  for (var i = 0; i < pointerCount; i++) {
2✔
244
    type = PointerType(type);
1✔
245
  }
246
  return type;
247
}
248

249
String makePostfixFromRawVarArgType(List<String> rawVarArgType) {
1✔
250
  return rawVarArgType
251
      .map((e) => e
2✔
252
          .replaceAll('*', 'Ptr')
1✔
253
          .replaceAll(RegExp(r'_t$'), '')
2✔
254
          .replaceAll(' ', '')
1✔
255
          .replaceAll(RegExp('[^A-Za-z0-9_]'), ''))
2✔
256
      .map((e) => e.length > 1 ? '${e[0].toUpperCase()}${e.substring(1)}' : e)
8✔
257
      .join('');
1✔
258
}
259

260
Type makeTypeFromRawVarArgType(
1✔
261
    String rawVarArgType, Map<String, LibraryImport> libraryImportsMap) {
262
  Type baseType;
263
  var rawBaseType = rawVarArgType.trim();
1✔
264
  // Split the raw type based on pointer usage. E.g -
265
  // int => [int]
266
  // char* => [char,*]
267
  // ffi.Hello ** => [ffi.Hello,**]
268
  final typeStringRegexp = RegExp(r'([a-zA-Z0-9_\s\.]+)(\**)$');
1✔
269
  if (!typeStringRegexp.hasMatch(rawBaseType)) {
1✔
270
    throw Exception('Cannot parse variadic argument type - $rawVarArgType.');
×
271
  }
272
  final regExpMatch = typeStringRegexp.firstMatch(rawBaseType)!;
1✔
273
  final groups = regExpMatch.groups([1, 2]);
2✔
274
  rawBaseType = groups[0]!;
1✔
275
  // Handle basic supported types.
276
  if (cxTypeKindToImportedTypes.containsKey(rawBaseType)) {
2✔
277
    baseType = cxTypeKindToImportedTypes[rawBaseType]!;
2✔
278
  } else if (supportedTypedefToImportedType.containsKey(rawBaseType)) {
2✔
279
    baseType = supportedTypedefToImportedType[rawBaseType]!;
×
280
  } else if (suportedTypedefToSuportedNativeType.containsKey(rawBaseType)) {
2✔
281
    baseType = NativeType(suportedTypedefToSuportedNativeType[rawBaseType]!);
×
282
  } else {
283
    // Use library import if specified (E.g - ffi.UintPtr or custom.MyStruct)
284
    final rawVarArgTypeSplit = rawBaseType.split('.');
1✔
285
    if (rawVarArgTypeSplit.length == 1) {
2✔
286
      final typeName = rawVarArgTypeSplit[0].replaceAll(' ', '');
2✔
287
      baseType = SelfImportedType(typeName, typeName);
1✔
288
    } else if (rawVarArgTypeSplit.length == 2) {
×
289
      final lib = rawVarArgTypeSplit[0];
×
290
      final libraryImport = strings.predefinedLibraryImports[lib] ??
×
291
          libraryImportsMap[rawVarArgTypeSplit[0]];
×
292
      if (libraryImport == null) {
293
        throw Exception('Please declare $lib in library-imports.');
×
294
      }
295
      final typeName = rawVarArgTypeSplit[1].replaceAll(' ', '');
×
296
      baseType = ImportedType(libraryImport, typeName, typeName);
×
297
    } else {
298
      throw Exception(
×
299
          'Invalid type $rawVarArgType : Expected 0 or 1 .(dot) separators.');
×
300
    }
301
  }
302

303
  // Handle pointers
304
  final pointerCount = groups[1]!.length;
2✔
305
  return makePointerToType(baseType, pointerCount);
1✔
306
}
307

308
Map<String, List<VarArgFunction>> makeVarArgFunctionsMapping(
50✔
309
    Map<String, List<RawVarArgFunction>> rawVarArgMappings,
310
    Map<String, LibraryImport> libraryImportsMap) {
311
  final mappings = <String, List<VarArgFunction>>{};
50✔
312
  for (final key in rawVarArgMappings.keys) {
51✔
313
    final varArgList = <VarArgFunction>[];
1✔
314
    for (final rawVarArg in rawVarArgMappings[key]!) {
3✔
315
      var postfix = rawVarArg.postfix ?? '';
1✔
316
      final types = <Type>[];
1✔
317
      for (final rva in rawVarArg.rawTypeStrings) {
2✔
318
        types.add(makeTypeFromRawVarArgType(rva, libraryImportsMap));
2✔
319
      }
320
      if (postfix.isEmpty) {
1✔
321
        if (rawVarArgMappings[key]!.length == 1) {
3✔
322
          postfix = '';
323
        } else {
324
          postfix = makePostfixFromRawVarArgType(rawVarArg.rawTypeStrings);
2✔
325
        }
326
      }
327
      // Extract postfix from config and/or deduce from var names.
328
      varArgList.add(VarArgFunction(postfix, types));
2✔
329
    }
330
    mappings[key] = varArgList;
1✔
331
  }
332
  return mappings;
333
}
334

335
final _quoteMatcher = RegExp(r'''^["'](.*)["']$''', dotAll: true);
18✔
336
final _cmdlineArgMatcher = RegExp(r'''['"](\\"|[^"])*?['"]|[^ ]+''');
18✔
337
List<String> compilerOptsToList(String compilerOpts) {
6✔
338
  final list = <String>[];
6✔
339
  _cmdlineArgMatcher.allMatches(compilerOpts).forEach((element) {
24✔
340
    var match = element.group(0);
6✔
341
    if (match != null) {
342
      if (quiver.matchesFull(_quoteMatcher, match)) {
12✔
343
        match = _quoteMatcher.allMatches(match).first.group(1)!;
4✔
344
      }
345
      list.add(match);
6✔
346
    }
347
  });
348

349
  return list;
350
}
351

352
List<String> compilerOptsExtractor(dynamic value) {
5✔
353
  if (value is String) {
5✔
354
    return compilerOptsToList(value);
4✔
355
  }
356

357
  final list = <String>[];
1✔
358
  for (final el in (value as List)) {
2✔
359
    if (el is String) {
1✔
360
      list.addAll(compilerOptsToList(el));
2✔
361
    }
362
  }
363
  return list;
364
}
365

366
Headers headersExtractor(
50✔
367
    Map<dynamic, List<String>> yamlConfig, String? configFilename) {
368
  final entryPoints = <String>[];
50✔
369
  final includeGlobs = <quiver.Glob>[];
50✔
370
  for (final key in yamlConfig.keys) {
100✔
371
    if (key == strings.entryPoints) {
50✔
372
      for (final h in (yamlConfig[key]!)) {
150✔
373
        final headerGlob = _normalizePath(h, configFilename);
50✔
374
        // Add file directly to header if it's not a Glob but a File.
375
        if (File(headerGlob).existsSync()) {
100✔
376
          final osSpecificPath = headerGlob;
377
          entryPoints.add(osSpecificPath);
35✔
378
          _logger.fine('Adding header/file: $headerGlob');
105✔
379
        } else {
380
          final glob = Glob(headerGlob);
15✔
381
          for (final file in glob.listFileSystemSync(const LocalFileSystem(),
15✔
382
              followLinks: true)) {
×
383
            final fixedPath = file.path;
×
384
            entryPoints.add(fixedPath);
×
385
            _logger.fine('Adding header/file: $fixedPath');
×
386
          }
387
        }
388
      }
389
    }
390
    if (key == strings.includeDirectives) {
50✔
391
      for (final h in yamlConfig[key]!) {
30✔
392
        final headerGlob = h;
393
        final fixedGlob = _normalizePath(headerGlob, configFilename);
10✔
394
        includeGlobs.add(quiver.Glob(fixedGlob));
20✔
395
      }
396
    }
397
  }
398
  return Headers(
50✔
399
    entryPoints: entryPoints,
400
    includeFilter: GlobHeaderFilter(
50✔
401
      includeGlobs: includeGlobs,
402
    ),
403
  );
404
}
405

406
/// Returns location of dynamic library by searching default locations. Logs
407
/// error and throws an Exception if not found.
408
String findDylibAtDefaultLocations() {
50✔
409
  String? k;
410
  if (Platform.isLinux) {
50✔
411
    for (final l in strings.linuxDylibLocations) {
×
412
      k = findLibclangDylib(l);
×
413
      if (k != null) return k;
414
    }
415
    Process.runSync('ldconfig', ['-p']);
×
416
    final ldConfigResult = Process.runSync('ldconfig', ['-p']);
×
417
    if (ldConfigResult.exitCode == 0) {
×
418
      final lines = (ldConfigResult.stdout as String).split('\n');
×
419
      final paths = [
×
420
        for (final line in lines)
×
421
          if (line.contains('libclang')) line.split(' => ')[1],
×
422
      ];
423
      for (final location in paths) {
×
424
        if (File(location).existsSync()) {
×
425
          return location;
426
        }
427
      }
428
    }
429
  } else if (Platform.isWindows) {
50✔
430
    final dylibLocations = strings.windowsDylibLocations.toList();
×
431
    final userHome = Platform.environment['USERPROFILE'];
×
432
    if (userHome != null) {
433
      dylibLocations
434
          .add(p.join(userHome, 'scoop', 'apps', 'llvm', 'current', 'bin'));
×
435
    }
436
    for (final l in dylibLocations) {
×
437
      k = findLibclangDylib(l);
×
438
      if (k != null) return k;
439
    }
440
  } else if (Platform.isMacOS) {
50✔
441
    for (final l in strings.macOsDylibLocations) {
100✔
442
      k = findLibclangDylib(l);
50✔
443
      if (k != null) return k;
444
    }
445
    final findLibraryResult =
446
        Process.runSync('xcodebuild', ['-find-library', 'libclang.dylib']);
×
447
    if (findLibraryResult.exitCode == 0) {
×
448
      final location = (findLibraryResult.stdout as String).split('\n').first;
×
449
      if (File(location).existsSync()) {
×
450
        return location;
451
      }
452
    }
453
    final xcodePathResult = Process.runSync('xcode-select', ['-print-path']);
×
454
    if (xcodePathResult.exitCode == 0) {
×
455
      final xcodePath = (xcodePathResult.stdout as String).split('\n').first;
×
456
      final location =
457
          p.join(xcodePath, strings.xcodeDylibLocation, strings.dylibFileName);
×
458
      if (File(location).existsSync()) {
×
459
        return location;
460
      }
461
    }
462
  } else {
463
    throw Exception('Unsupported Platform.');
×
464
  }
465

466
  _logger.severe("Couldn't find dynamic library in default locations.");
×
467
  _logger.severe(
×
468
      "Please supply one or more path/to/llvm in ffigen's config under the key '${strings.llvmPath}'.");
×
469
  throw Exception("Couldn't find dynamic library in default locations.");
×
470
}
471

472
String? findLibclangDylib(String parentFolder) {
50✔
473
  final location = p.join(parentFolder, strings.dylibFileName);
100✔
474
  if (File(location).existsSync()) {
100✔
475
    return location;
476
  } else {
477
    return null;
478
  }
479
}
480

481
String llvmPathExtractor(List<String> value) {
×
482
  // Extract libclang's dylib from user specified paths.
483
  for (final path in value) {
×
484
    final dylibPath =
485
        findLibclangDylib(p.join(path, strings.dynamicLibParentName));
×
486
    if (dylibPath != null) {
487
      _logger.fine('Found dynamic library at: $dylibPath');
×
488
      return dylibPath;
489
    }
490
    // Check if user has specified complete path to dylib.
491
    final completeDylibPath = path;
492
    if (p.extension(completeDylibPath).isNotEmpty &&
×
493
        File(completeDylibPath).existsSync()) {
×
494
      _logger.info(
×
495
          'Using complete dylib path: $completeDylibPath from llvm-path.');
×
496
      return completeDylibPath;
497
    }
498
  }
499
  _logger.fine(
×
500
      "Couldn't find dynamic library under paths specified by ${strings.llvmPath}.");
×
501
  // Extract path from default locations.
502
  try {
503
    final res = findDylibAtDefaultLocations();
×
504
    return res;
505
  } catch (e) {
506
    _logger.severe(
×
507
        "Couldn't find ${p.join(strings.dynamicLibParentName, strings.dylibFileName)} in specified locations.");
×
508
    exit(1);
×
509
  }
510
}
511

512
OutputConfig outputExtractor(
50✔
513
    dynamic value, String? configFilename, PackageConfig? packageConfig) {
514
  if (value is String) {
50✔
515
    return OutputConfig(_normalizePath(value, configFilename), null);
100✔
516
  }
517
  value = value as Map;
518
  return OutputConfig(
1✔
519
    _normalizePath((value)[strings.bindings] as String, configFilename),
2✔
520
    value.containsKey(strings.symbolFile)
1✔
521
        ? symbolFileOutputExtractor(
1✔
522
            value[strings.symbolFile], configFilename, packageConfig)
1✔
523
        : null,
524
  );
525
}
526

527
SymbolFile symbolFileOutputExtractor(
1✔
528
    dynamic value, String? configFilename, PackageConfig? packageConfig) {
529
  value = value as Map;
530
  var output = value[strings.output] as String;
1✔
531
  if (Uri.parse(output).scheme != "package") {
3✔
532
    _logger.warning(
×
533
        'Consider using a Package Uri for ${strings.symbolFile} -> ${strings.output}: $output so that external packages can use it.');
×
534
    output = _normalizePath(output, configFilename);
×
535
  } else {
536
    output = packageConfig!.resolve(Uri.parse(output))!.toFilePath();
3✔
537
  }
538
  final importPath = value[strings.importPath] as String;
1✔
539
  if (Uri.parse(importPath).scheme != "package") {
3✔
540
    _logger.warning(
×
541
        'Consider using a Package Uri for ${strings.symbolFile} -> ${strings.importPath}: $importPath so that external packages can use it.');
×
542
  }
543
  return SymbolFile(importPath, output);
1✔
544
}
545

546
/// Returns true if [str] is not a full name.
547
///
548
/// E.g `abc` is a full name, `abc.*` is not.
549
bool isFullDeclarationName(String str) =>
27✔
550
    quiver.matchesFull(RegExp('[a-zA-Z_0-9]*'), str);
54✔
551

552
Includer extractIncluderFromYaml(dynamic yamlMap) {
50✔
553
  final includeMatchers = <RegExp>[],
50✔
554
      includeFull = <String>{},
555
      excludeMatchers = <RegExp>[],
50✔
556
      excludeFull = <String>{};
557

558
  final include = (yamlMap[strings.include] as YamlList?)?.cast<String>();
72✔
559
  if (include != null) {
560
    if (include.isEmpty) {
22✔
561
      return Includer.excludeByDefault();
×
562
    }
563
    for (final str in include) {
44✔
564
      if (isFullDeclarationName(str)) {
22✔
565
        includeFull.add(str);
22✔
566
      } else {
567
        includeMatchers.add(RegExp(str, dotAll: true));
2✔
568
      }
569
    }
570
  }
571

572
  final exclude = (yamlMap[strings.exclude] as YamlList?)?.cast<String>();
56✔
573
  if (exclude != null) {
574
    for (final str in exclude) {
12✔
575
      if (isFullDeclarationName(str)) {
6✔
576
        excludeFull.add(str);
6✔
577
      } else {
578
        excludeMatchers.add(RegExp(str, dotAll: true));
×
579
      }
580
    }
581
  }
582

583
  return Includer(
50✔
584
    includeMatchers: includeMatchers,
585
    includeFull: includeFull,
586
    excludeMatchers: excludeMatchers,
587
    excludeFull: excludeFull,
588
  );
589
}
590

591
Map<String, List<RawVarArgFunction>> varArgFunctionConfigExtractor(
1✔
592
    Map<dynamic, dynamic> yamlMap) {
593
  final result = <String, List<RawVarArgFunction>>{};
1✔
594
  final configMap = yamlMap;
595
  for (final key in configMap.keys) {
2✔
596
    final List<RawVarArgFunction> vafuncs = [];
1✔
597
    for (final rawVaFunc in (configMap[key] as List)) {
3✔
598
      if (rawVaFunc is List) {
1✔
599
        vafuncs.add(RawVarArgFunction(null, rawVaFunc.cast()));
3✔
600
      } else if (rawVaFunc is Map) {
1✔
601
        vafuncs.add(RawVarArgFunction(rawVaFunc[strings.postfix] as String?,
3✔
602
            (rawVaFunc[strings.types] as List).cast()));
2✔
603
      } else {
604
        throw Exception("Unexpected type in variadic-argument config.");
×
605
      }
606
    }
607
    result[key as String] = vafuncs;
1✔
608
  }
609

610
  return result;
611
}
612

613
// TODO: maybe remove yaml dependency
614
Declaration declarationConfigExtractor(dynamic yamlMap) {
50✔
615
  final renamePatterns = <RegExpRenamer>[];
50✔
616
  final renameFull = <String, String>{};
50✔
617
  final memberRenamePatterns = <RegExpMemberRenamer>[];
50✔
618
  final memberRenamerFull = <String, Renamer>{};
50✔
619

620
  final includer = extractIncluderFromYaml(yamlMap);
50✔
621

622
  Includer? symbolIncluder;
623
  if (yamlMap[strings.symbolAddress] != null) {
50✔
624
    symbolIncluder = extractIncluderFromYaml(yamlMap[strings.symbolAddress]);
6✔
625
  }
626

627
  final rename = (yamlMap[strings.rename] as YamlMap?)?.cast<String, String>();
52✔
628

629
  if (rename != null) {
630
    for (final str in rename.keys) {
4✔
631
      if (isFullDeclarationName(str)) {
2✔
632
        renameFull[str] = rename[str]!;
2✔
633
      } else {
634
        renamePatterns
635
            .add(RegExpRenamer(RegExp(str, dotAll: true), rename[str]!));
8✔
636
      }
637
    }
638
  }
639

640
  final memberRename =
641
      (yamlMap[strings.memberRename] as YamlMap?)?.cast<String, YamlMap>();
51✔
642

643
  if (memberRename != null) {
644
    for (final decl in memberRename.keys) {
2✔
645
      final renamePatterns = <RegExpRenamer>[];
1✔
646
      final renameFull = <String, String>{};
1✔
647

648
      final memberRenameMap = memberRename[decl]!.cast<String, String>();
2✔
649
      for (final member in memberRenameMap.keys) {
2✔
650
        if (isFullDeclarationName(member)) {
1✔
651
          renameFull[member] = memberRenameMap[member]!;
2✔
652
        } else {
653
          renamePatterns.add(RegExpRenamer(
2✔
654
              RegExp(member, dotAll: true), memberRenameMap[member]!));
2✔
655
        }
656
      }
657
      if (isFullDeclarationName(decl)) {
1✔
658
        memberRenamerFull[decl] = Renamer(
2✔
659
          renameFull: renameFull,
660
          renamePatterns: renamePatterns,
661
        );
662
      } else {
663
        memberRenamePatterns.add(
1✔
664
          RegExpMemberRenamer(
1✔
665
            RegExp(decl, dotAll: true),
1✔
666
            Renamer(
1✔
667
              renameFull: renameFull,
668
              renamePatterns: renamePatterns,
669
            ),
670
          ),
671
        );
672
      }
673
    }
674
  }
675

676
  return Declaration(
50✔
677
    includer: includer,
678
    renamer: Renamer(
50✔
679
      renameFull: renameFull,
680
      renamePatterns: renamePatterns,
681
    ),
682
    memberRenamer: MemberRenamer(
50✔
683
      memberRenameFull: memberRenamerFull,
684
      memberRenamePattern: memberRenamePatterns,
685
    ),
686
    symbolAddressIncluder: symbolIncluder,
687
  );
688
}
689

690
Includer leafFunctionExtractor(dynamic value) => extractIncluderFromYaml(value);
×
691

692
bool leafFunctionValidator(List<String> name, dynamic value) {
×
693
  var result = true;
694

695
  if (!checkType<YamlMap>(name, value)) {
×
696
    result = false;
697
  } else {
698
    final mp = value as YamlMap;
699
    for (final key in mp.keys) {
×
700
      if (key == strings.include || key == strings.exclude) {
×
701
        if (!checkType<YamlList>([...name, key as String], value[key])) {
×
702
          result = false;
703
        }
704
      } else {
705
        _logger.severe("Unknown subkey '$key' in '$name'.");
×
706
        result = false;
707
      }
708
    }
709
  }
710

711
  return result;
712
}
713

714
SupportedNativeType nativeSupportedType(int value, {bool signed = true}) {
×
715
  switch (value) {
716
    case 1:
×
717
      return signed ? SupportedNativeType.Int8 : SupportedNativeType.Uint8;
718
    case 2:
×
719
      return signed ? SupportedNativeType.Int16 : SupportedNativeType.Uint16;
720
    case 4:
×
721
      return signed ? SupportedNativeType.Int32 : SupportedNativeType.Uint32;
722
    case 8:
×
723
      return signed ? SupportedNativeType.Int64 : SupportedNativeType.Uint64;
724
    default:
725
      throw Exception(
×
726
          'Unsupported value given to sizemap, Allowed values for sizes are: 1, 2, 4, 8');
727
  }
728
}
729

730
bool nonEmptyStringValidator(List<String> name, dynamic value) {
×
731
  if (value is String && value.isNotEmpty) {
×
732
    return true;
733
  } else {
734
    _logger.severe("Expected value of key '$name' to be a non-empty String.");
×
735
    return false;
736
  }
737
}
738

739
bool dartClassNameValidator(List<String> name, dynamic value) {
×
740
  if (value is String &&
×
741
      quiver.matchesFull(RegExp('[a-zA-Z]+[_a-zA-Z0-9]*'), value)) {
×
742
    return true;
743
  } else {
744
    _logger.severe(
×
745
        "Expected value of key '$name' to be a valid public class name.");
×
746
    return false;
747
  }
748
}
749

750
StructPackingOverride structPackingOverrideExtractor(
1✔
751
    Map<dynamic, dynamic> value) {
752
  final matcherMap = <RegExp, int?>{};
1✔
753
  for (final key in value.keys) {
2✔
754
    matcherMap[RegExp(key as String, dotAll: true)] =
2✔
755
        strings.packingValuesMap[value[key]];
2✔
756
  }
757
  return StructPackingOverride(matcherMap: matcherMap);
1✔
758
}
759

760
FfiNativeConfig ffiNativeExtractor(dynamic yamlConfig) {
1✔
761
  final yamlMap = yamlConfig as Map?;
762
  return FfiNativeConfig(
1✔
763
    enabled: true,
764
    asset: yamlMap?[strings.ffiNativeAsset] as String?,
×
765
  );
766
}
767

768
bool ffiNativeValidator(List<String> name, dynamic yamlConfig) {
×
769
  if (!checkType<YamlMap?>(name, yamlConfig)) {
×
770
    return false;
771
  }
772
  if (yamlConfig == null) {
773
    // Empty means no asset name.
774
    return true;
775
  }
776
  for (final key in (yamlConfig as YamlMap).keys) {
×
777
    if (!checkType<String>([...name, key as String], yamlConfig[key])) {
×
778
      return false;
779
    }
780
    if (key != strings.ffiNativeAsset) {
×
781
      _logger.severe("'$name -> $key' must be one of the following - ${[
×
782
        strings.ffiNativeAsset
783
      ]}");
×
784
    }
785
  }
786
  return true;
787
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc