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

dart-lang / ffigen / 4364636722

pending completion
4364636722

Pull #527

github

GitHub
Merge 984472b2c into 1ac20ffd5
Pull Request #527: Bump dependencies and fix lints

21 of 21 new or added lines in 10 files covered. (100.0%)

3188 of 3452 relevant lines covered (92.35%)

24.67 hits per line

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

75.1
/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:file/local.dart';
10
import 'package:glob/glob.dart';
11
import 'package:logging/logging.dart';
12
import 'package:package_config/package_config.dart';
13
import 'package:path/path.dart' as p;
14
import 'package:quiver/pattern.dart' as quiver;
15
import 'package:yaml/yaml.dart';
16

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

20
final _logger = Logger('ffigen.config_provider.spec_utils');
144✔
21

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

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

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

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

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

78
  return null;
79
}
80

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

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

116
bool booleanExtractor(dynamic value) => value as bool;
18✔
117

118
bool booleanValidator(List<String> name, dynamic value) =>
18✔
119
    checkType<bool>(name, value);
18✔
120

121
Map<String, LibraryImport> libraryImportsExtractor(dynamic yamlConfig) {
1✔
122
  final resultMap = <String, LibraryImport>{};
1✔
123
  final typeMap = yamlConfig as YamlMap?;
124
  if (typeMap != null) {
125
    for (final typeName in typeMap.keys) {
2✔
126
      resultMap[typeName as String] =
1✔
127
          LibraryImport(typeName, typeMap[typeName] as String);
2✔
128
    }
129
  }
130
  return resultMap;
131
}
132

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

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

161
YamlMap loadSymbolFile(String symbolFilePath, String? configFileName,
1✔
162
    PackageConfig? packageConfig) {
163
  final path = symbolFilePath.startsWith('package:')
1✔
164
      ? packageConfig!.resolve(Uri.parse(symbolFilePath))!.toFilePath()
×
165
      : _normalizePath(symbolFilePath, configFileName);
1✔
166

167
  return loadYaml(File(path).readAsStringSync()) as YamlMap;
3✔
168
}
169

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

212
bool symbolFileImportValidator(List<String> name, dynamic yamlConfig) {
1✔
213
  if (!checkType<YamlList>(name, yamlConfig)) {
1✔
214
    return false;
215
  }
216
  var result = true;
217
  (yamlConfig as YamlList).asMap().forEach((idx, value) {
3✔
218
    if (value is YamlMap) {
1✔
219
      if (!value.keys.contains(strings.symbolFile)) {
×
220
        result = false;
221
        _logger
×
222
            .severe('Key $name -> $idx -> ${strings.symbolFile} is required.');
×
223
      }
224
      for (final key in value.keys) {
×
225
        if (key == strings.symbolFile) {
×
226
          if (!checkType<String>(
×
227
              [...name, idx.toString(), key as String], value[key])) {
×
228
            result = false;
229
          }
230
        } else {
231
          result = false;
232
          _logger.severe('Unknown key : $name -> $idx -> $key.');
×
233
        }
234
      }
235
    } else if (value is! String) {
1✔
236
      result = false;
237
      _logger.severe('Expected $name -> $idx should be a String or Map.');
×
238
    }
239
  });
240
  return result;
241
}
242

243
Map<String, List<String>> typeMapExtractor(dynamic yamlConfig) {
3✔
244
  // Key - type_name, Value - [lib, cType, dartType].
245
  final resultMap = <String, List<String>>{};
3✔
246
  final typeMap = yamlConfig as YamlMap?;
247
  if (typeMap != null) {
248
    for (final typeName in typeMap.keys) {
6✔
249
      final typeConfigItem = typeMap[typeName] as YamlMap;
3✔
250
      resultMap[typeName as String] = [
6✔
251
        typeConfigItem[strings.lib] as String,
3✔
252
        typeConfigItem[strings.cType] as String,
3✔
253
        typeConfigItem[strings.dartType] as String,
3✔
254
      ];
255
    }
256
  }
257
  return resultMap;
258
}
259

260
bool typeMapValidator(List<String> name, dynamic yamlConfig) {
3✔
261
  if (!checkType<YamlMap>(name, yamlConfig)) {
3✔
262
    return false;
263
  }
264
  var result = true;
265
  for (final key in (yamlConfig as YamlMap).keys) {
6✔
266
    if (!checkType<YamlMap>([...name, key as String], yamlConfig[key])) {
12✔
267
      return false;
268
    }
269
    final lib = (yamlConfig[key] as YamlMap).containsKey(strings.lib);
6✔
270
    if (!lib) {
271
      _logger.severe("Key '${strings.lib}' in $name -> $key is required.");
×
272
      result = false;
273
    }
274
    final cType = (yamlConfig[key] as YamlMap).containsKey(strings.cType);
6✔
275
    if (!cType) {
276
      _logger.severe("Key '${strings.cType}' in $name -> $key is required.");
×
277
      result = false;
278
    }
279
    final dartType = (yamlConfig[key] as YamlMap).containsKey(strings.dartType);
6✔
280
    if (!dartType) {
281
      _logger.severe("Key '${strings.dartType}' in $name -> $key is required.");
×
282
      result = false;
283
    }
284
  }
285
  return result;
286
}
287

288
Map<String, String> stringStringMapExtractor(dynamic yamlConfig) {
2✔
289
  final resultMap = <String, String>{};
2✔
290
  final inputMap = yamlConfig as YamlMap?;
291
  if (inputMap != null) {
292
    for (final key in inputMap.keys) {
4✔
293
      resultMap[key as String] = inputMap[key] as String;
4✔
294
    }
295
  }
296
  return resultMap;
297
}
298

299
bool stringStringMapValidator(List<String> name, dynamic yamlConfig) {
2✔
300
  if (!checkType<YamlMap>(name, yamlConfig)) {
2✔
301
    return false;
302
  }
303
  for (final key in (yamlConfig as YamlMap).keys) {
4✔
304
    if (!checkType<String>([...name, key as String], yamlConfig[key])) {
8✔
305
      return false;
306
    }
307
  }
308
  return true;
309
}
310

311
Map<String, ImportedType> makeImportTypeMapping(
48✔
312
    Map<String, List<String>> rawTypeMappings,
313
    Map<String, LibraryImport> libraryImportsMap) {
314
  final typeMappings = <String, ImportedType>{};
48✔
315
  for (final key in rawTypeMappings.keys) {
51✔
316
    final lib = rawTypeMappings[key]![0];
6✔
317
    final cType = rawTypeMappings[key]![1];
6✔
318
    final dartType = rawTypeMappings[key]![2];
6✔
319
    if (strings.predefinedLibraryImports.containsKey(lib)) {
6✔
320
      typeMappings[key] =
3✔
321
          ImportedType(strings.predefinedLibraryImports[lib]!, cType, dartType);
9✔
322
    } else if (libraryImportsMap.containsKey(lib)) {
1✔
323
      typeMappings[key] =
1✔
324
          ImportedType(libraryImportsMap[lib]!, cType, dartType);
2✔
325
    } else {
326
      throw Exception("Please declare $lib under library-imports.");
×
327
    }
328
  }
329
  return typeMappings;
330
}
331

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

346
  return list;
347
}
348

349
List<String> compilerOptsExtractor(dynamic value) {
5✔
350
  if (value is String) {
5✔
351
    return compilerOptsToList(value);
5✔
352
  }
353

354
  final list = <String>[];
×
355
  for (final el in (value as YamlList)) {
×
356
    if (el is String) {
×
357
      list.addAll(compilerOptsToList(el));
×
358
    }
359
  }
360
  return list;
361
}
362

363
bool compilerOptsValidator(List<String> name, dynamic value) {
5✔
364
  if (value is String || value is YamlList) {
5✔
365
    return true;
366
  } else {
367
    _logger.severe('Expected $name to be a String or List of String.');
×
368
    return false;
369
  }
370
}
371

372
CompilerOptsAuto compilerOptsAutoExtractor(dynamic value) {
1✔
373
  return CompilerOptsAuto(
1✔
374
    macIncludeStdLib: getKeyValueFromYaml(
1✔
375
      [strings.macos, strings.includeCStdLib],
1✔
376
      value as YamlMap,
377
    ) as bool?,
378
  );
379
}
380

381
bool compilerOptsAutoValidator(List<String> name, dynamic value) {
1✔
382
  var result = true;
383

384
  if (!checkType<YamlMap>(name, value)) {
1✔
385
    return false;
386
  }
387

388
  for (final oskey in (value as YamlMap).keys) {
2✔
389
    if (oskey == strings.macos) {
1✔
390
      if (!checkType<YamlMap>([...name, oskey as String], value[oskey])) {
4✔
391
        return false;
392
      }
393

394
      for (final inckey in (value[oskey] as YamlMap).keys) {
3✔
395
        if (inckey == strings.includeCStdLib) {
1✔
396
          if (!checkType<bool>(
1✔
397
              [...name, oskey, inckey as String], value[oskey][inckey])) {
5✔
398
            result = false;
399
          }
400
        } else {
401
          _logger.severe("Unknown key '$inckey' in '$name -> $oskey.");
×
402
          result = false;
403
        }
404
      }
405
    } else {
406
      _logger.severe("Unknown key '$oskey' in '$name'.");
×
407
      result = false;
408
    }
409
  }
410
  return result;
411
}
412

413
Headers headersExtractor(dynamic yamlConfig, String? configFilename) {
48✔
414
  final entryPoints = <String>[];
48✔
415
  final includeGlobs = <quiver.Glob>[];
48✔
416
  for (final key in (yamlConfig as YamlMap).keys) {
96✔
417
    if (key == strings.entryPoints) {
48✔
418
      for (final h in (yamlConfig[key] as YamlList)) {
144✔
419
        final headerGlob = _normalizePath(h as String, configFilename);
48✔
420
        // Add file directly to header if it's not a Glob but a File.
421
        if (File(headerGlob).existsSync()) {
96✔
422
          final osSpecificPath = headerGlob;
423
          entryPoints.add(osSpecificPath);
33✔
424
          _logger.fine('Adding header/file: $headerGlob');
99✔
425
        } else {
426
          final glob = Glob(headerGlob);
15✔
427
          for (final file in glob.listFileSystemSync(const LocalFileSystem(),
15✔
428
              followLinks: true)) {
×
429
            final fixedPath = file.path;
×
430
            entryPoints.add(fixedPath);
×
431
            _logger.fine('Adding header/file: $fixedPath');
×
432
          }
433
        }
434
      }
435
    }
436
    if (key == strings.includeDirectives) {
48✔
437
      for (final h in (yamlConfig[key] as YamlList)) {
30✔
438
        final headerGlob = h as String;
439
        final fixedGlob = _normalizePath(headerGlob, configFilename);
10✔
440
        includeGlobs.add(quiver.Glob(fixedGlob));
20✔
441
      }
442
    }
443
  }
444
  return Headers(
48✔
445
    entryPoints: entryPoints,
446
    includeFilter: GlobHeaderFilter(
48✔
447
      includeGlobs: includeGlobs,
448
    ),
449
  );
450
}
451

452
bool headersValidator(List<String> name, dynamic value) {
48✔
453
  if (!checkType<YamlMap>(name, value)) {
48✔
454
    return false;
455
  }
456
  if (!(value as YamlMap).containsKey(strings.entryPoints)) {
48✔
457
    _logger.severe("Required '$name -> ${strings.entryPoints}'.");
×
458
    return false;
459
  } else {
460
    for (final key in value.keys) {
96✔
461
      if (key == strings.entryPoints || key == strings.includeDirectives) {
58✔
462
        if (!checkType<YamlList>([...name, key as String], value[key])) {
192✔
463
          return false;
464
        }
465
      } else {
466
        _logger.severe("Unknown key '$key' in '$name'.");
×
467
        return false;
468
      }
469
    }
470
    return true;
471
  }
472
}
473

474
/// Returns location of dynamic library by searching default locations. Logs
475
/// error and throws an Exception if not found.
476
String findDylibAtDefaultLocations() {
48✔
477
  String? k;
478
  if (Platform.isLinux) {
48✔
479
    for (final l in strings.linuxDylibLocations) {
×
480
      k = findLibclangDylib(l);
×
481
      if (k != null) return k;
482
    }
483
    Process.runSync('ldconfig', ['-p']);
×
484
    final ldConfigResult = Process.runSync('ldconfig', ['-p']);
×
485
    if (ldConfigResult.exitCode == 0) {
×
486
      final lines = (ldConfigResult.stdout as String).split('\n');
×
487
      final paths = [
×
488
        for (final line in lines)
×
489
          if (line.contains('libclang')) line.split(' => ')[1],
×
490
      ];
491
      for (final location in paths) {
×
492
        if (File(location).existsSync()) {
×
493
          return location;
494
        }
495
      }
496
    }
497
  } else if (Platform.isWindows) {
48✔
498
    for (final l in strings.windowsDylibLocations) {
×
499
      k = findLibclangDylib(l);
×
500
      if (k != null) return k;
501
    }
502
  } else if (Platform.isMacOS) {
48✔
503
    for (final l in strings.macOsDylibLocations) {
96✔
504
      k = findLibclangDylib(l);
48✔
505
      if (k != null) return k;
506
    }
507
    final findLibraryResult =
508
        Process.runSync('xcodebuild', ['-find-library', 'libclang.dylib']);
×
509
    if (findLibraryResult.exitCode == 0) {
×
510
      final location = (findLibraryResult.stdout as String).split('\n').first;
×
511
      if (File(location).existsSync()) {
×
512
        return location;
513
      }
514
    }
515
    final xcodePathResult = Process.runSync('xcode-select', ['-print-path']);
×
516
    if (xcodePathResult.exitCode == 0) {
×
517
      final xcodePath = (xcodePathResult.stdout as String).split('\n').first;
×
518
      final location =
519
          p.join(xcodePath, strings.xcodeDylibLocation, strings.dylibFileName);
×
520
      if (File(location).existsSync()) {
×
521
        return location;
522
      }
523
    }
524
  } else {
525
    throw Exception('Unsupported Platform.');
×
526
  }
527

528
  _logger.severe("Couldn't find dynamic library in default locations.");
×
529
  _logger.severe(
×
530
      "Please supply one or more path/to/llvm in ffigen's config under the key '${strings.llvmPath}'.");
×
531
  throw Exception("Couldn't find dynamic library in default locations.");
×
532
}
533

534
String? findLibclangDylib(String parentFolder) {
48✔
535
  final location = p.join(parentFolder, strings.dylibFileName);
96✔
536
  if (File(location).existsSync()) {
96✔
537
    return location;
538
  } else {
539
    return null;
540
  }
541
}
542

543
String llvmPathExtractor(dynamic value) {
×
544
  // Extract libclang's dylib from user specified paths.
545
  for (final path in (value as YamlList)) {
×
546
    if (path is! String) continue;
×
547
    final dylibPath =
548
        findLibclangDylib(p.join(path, strings.dynamicLibParentName));
×
549
    if (dylibPath != null) {
550
      _logger.fine('Found dynamic library at: $dylibPath');
×
551
      return dylibPath;
552
    }
553
    // Check if user has specified complete path to dylib.
554
    final completeDylibPath = path;
555
    if (p.extension(completeDylibPath).isNotEmpty &&
×
556
        File(completeDylibPath).existsSync()) {
×
557
      _logger.info(
×
558
          'Using complete dylib path: $completeDylibPath from llvm-path.');
×
559
      return completeDylibPath;
560
    }
561
  }
562
  _logger.fine(
×
563
      "Couldn't find dynamic library under paths specified by ${strings.llvmPath}.");
×
564
  // Extract path from default locations.
565
  try {
566
    final res = findDylibAtDefaultLocations();
×
567
    return res;
568
  } catch (e) {
569
    _logger.severe(
×
570
        "Couldn't find ${p.join(strings.dynamicLibParentName, strings.dylibFileName)} in specified locations.");
×
571
    exit(1);
×
572
  }
573
}
574

575
bool llvmPathValidator(List<String> name, dynamic value) {
×
576
  if (!checkType<YamlList>(name, value)) {
×
577
    return false;
578
  }
579
  return true;
580
}
581

582
OutputConfig outputExtractor(
48✔
583
    dynamic value, String? configFilename, PackageConfig? packageConfig) {
584
  if (value is String) {
48✔
585
    return OutputConfig(_normalizePath(value, configFilename), null);
96✔
586
  }
587
  value = value as YamlMap;
588
  return OutputConfig(
1✔
589
    _normalizePath((value)[strings.bindings] as String, configFilename),
2✔
590
    value.containsKey(strings.symbolFile)
1✔
591
        ? symbolFileOutputExtractor(
1✔
592
            value[strings.symbolFile], configFilename, packageConfig)
1✔
593
        : null,
594
  );
595
}
596

597
bool outputValidator(List<String> name, dynamic value) {
48✔
598
  if (value is String) {
48✔
599
    return true;
600
  } else if (value is YamlMap) {
1✔
601
    final keys = value.keys;
1✔
602
    var result = true;
603
    for (final key in keys) {
2✔
604
      if (key == strings.bindings) {
1✔
605
        if (!checkType<String>([...name, key as String], value[key])) {
4✔
606
          result = false;
607
        }
608
      } else if (key == strings.symbolFile) {
1✔
609
        result = symbolFileOutputValidator(
1✔
610
            [...name, strings.symbolFile], value[key]);
3✔
611
      } else {
612
        result = false;
613
        _logger.severe("Unknown key '$key' in '$name'.");
×
614
      }
615
    }
616
    return result;
617
  } else {
618
    _logger.severe(
×
619
        "Expected value of key '${name.join(' -> ')}' to be a String or Map.");
×
620
    return false;
621
  }
622
}
623

624
SymbolFile symbolFileOutputExtractor(
1✔
625
    dynamic value, String? configFilename, PackageConfig? packageConfig) {
626
  value = value as YamlMap;
627
  var output = value[strings.output] as String;
1✔
628
  if (Uri.parse(output).scheme != "package") {
3✔
629
    _logger.warning(
2✔
630
        'Consider using a Package Uri for ${strings.symbolFile} -> ${strings.output}: $output so that external packages can use it.');
1✔
631
    output = _normalizePath(output, configFilename);
1✔
632
  } else {
633
    output = packageConfig!.resolve(Uri.parse(output))!.toFilePath();
×
634
  }
635
  final importPath = value[strings.importPath] as String;
1✔
636
  if (Uri.parse(importPath).scheme != "package") {
3✔
637
    _logger.warning(
×
638
        'Consider using a Package Uri for ${strings.symbolFile} -> ${strings.importPath}: $importPath so that external packages can use it.');
×
639
  }
640
  return SymbolFile(importPath, output);
1✔
641
}
642

643
bool symbolFileOutputValidator(List<String> name, dynamic value) {
1✔
644
  if (!checkType<YamlMap>(name, value)) {
1✔
645
    return false;
646
  }
647
  if (!(value as YamlMap).containsKey(strings.output)) {
1✔
648
    _logger.severe("Required '$name -> ${strings.output}'.");
×
649
    return false;
650
  }
651
  if (!(value).containsKey(strings.importPath)) {
1✔
652
    _logger.severe("Required '$name -> ${strings.importPath}'.");
×
653
    return false;
654
  }
655
  for (final key in value.keys) {
2✔
656
    if (key == strings.output || key == strings.importPath) {
2✔
657
      if (!checkType<String>([...name, key as String], value[key])) {
4✔
658
        return false;
659
      }
660
    } else {
661
      _logger.severe("Unknown key '$key' in '$name'.");
×
662
      return false;
663
    }
664
  }
665
  return true;
666
}
667

668
Language languageExtractor(dynamic value) {
17✔
669
  if (value == strings.langC) {
17✔
670
    return Language.c;
671
  } else if (value == strings.langObjC) {
17✔
672
    return Language.objc;
673
  }
674
  return Language.c;
675
}
676

677
bool languageValidator(List<String> name, dynamic value) {
17✔
678
  if (value is String) {
17✔
679
    if (value == strings.langC) {
17✔
680
      return true;
681
    }
682
    if (value == strings.langObjC) {
17✔
683
      _logger.severe('Objective C support is EXPERIMENTAL. The API may change '
34✔
684
          'in a breaking way without notice.');
685
      return true;
686
    }
687
    _logger.severe("'$name' must be one of the following - "
×
688
        "{${strings.langC}, ${strings.langObjC}}");
689
    return false;
690
  }
691
  _logger.severe("Expected value of key '$name' to be a String.");
×
692
  return false;
693
}
694

695
/// Returns true if [str] is not a full name.
696
///
697
/// E.g `abc` is a full name, `abc.*` is not.
698
bool isFullDeclarationName(String str) =>
27✔
699
    quiver.matchesFull(RegExp('[a-zA-Z_0-9]*'), str);
54✔
700

701
Includer _extractIncluderFromYaml(dynamic yamlMap) {
29✔
702
  final includeMatchers = <RegExp>[],
29✔
703
      includeFull = <String>{},
704
      excludeMatchers = <RegExp>[],
29✔
705
      excludeFull = <String>{};
706

707
  final include = (yamlMap[strings.include] as YamlList?)?.cast<String>();
51✔
708
  if (include != null) {
709
    if (include.isEmpty) {
22✔
710
      return Includer.excludeByDefault();
×
711
    }
712
    for (final str in include) {
44✔
713
      if (isFullDeclarationName(str)) {
22✔
714
        includeFull.add(str);
22✔
715
      } else {
716
        includeMatchers.add(RegExp(str, dotAll: true));
2✔
717
      }
718
    }
719
  }
720

721
  final exclude = (yamlMap[strings.exclude] as YamlList?)?.cast<String>();
35✔
722
  if (exclude != null) {
723
    for (final str in exclude) {
12✔
724
      if (isFullDeclarationName(str)) {
6✔
725
        excludeFull.add(str);
6✔
726
      } else {
727
        excludeMatchers.add(RegExp(str, dotAll: true));
×
728
      }
729
    }
730
  }
731

732
  return Includer(
29✔
733
    includeMatchers: includeMatchers,
734
    includeFull: includeFull,
735
    excludeMatchers: excludeMatchers,
736
    excludeFull: excludeFull,
737
  );
738
}
739

740
Declaration declarationConfigExtractor(dynamic yamlMap) {
29✔
741
  final renamePatterns = <RegExpRenamer>[];
29✔
742
  final renameFull = <String, String>{};
29✔
743
  final memberRenamePatterns = <RegExpMemberRenamer>[];
29✔
744
  final memberRenamerFull = <String, Renamer>{};
29✔
745

746
  final includer = _extractIncluderFromYaml(yamlMap);
29✔
747

748
  Includer? symbolIncluder;
749
  if (yamlMap[strings.symbolAddress] != null) {
29✔
750
    symbolIncluder = _extractIncluderFromYaml(yamlMap[strings.symbolAddress]);
6✔
751
  }
752

753
  final rename = (yamlMap[strings.rename] as YamlMap?)?.cast<String, String>();
31✔
754

755
  if (rename != null) {
756
    for (final str in rename.keys) {
4✔
757
      if (isFullDeclarationName(str)) {
2✔
758
        renameFull[str] = rename[str]!;
2✔
759
      } else {
760
        renamePatterns
761
            .add(RegExpRenamer(RegExp(str, dotAll: true), rename[str]!));
8✔
762
      }
763
    }
764
  }
765

766
  final memberRename =
767
      (yamlMap[strings.memberRename] as YamlMap?)?.cast<String, YamlMap>();
30✔
768

769
  if (memberRename != null) {
770
    for (final decl in memberRename.keys) {
2✔
771
      final renamePatterns = <RegExpRenamer>[];
1✔
772
      final renameFull = <String, String>{};
1✔
773

774
      final memberRenameMap = memberRename[decl]!.cast<String, String>();
2✔
775
      for (final member in memberRenameMap.keys) {
2✔
776
        if (isFullDeclarationName(member)) {
1✔
777
          renameFull[member] = memberRenameMap[member]!;
2✔
778
        } else {
779
          renamePatterns.add(RegExpRenamer(
2✔
780
              RegExp(member, dotAll: true), memberRenameMap[member]!));
2✔
781
        }
782
      }
783
      if (isFullDeclarationName(decl)) {
1✔
784
        memberRenamerFull[decl] = Renamer(
2✔
785
          renameFull: renameFull,
786
          renamePatterns: renamePatterns,
787
        );
788
      } else {
789
        memberRenamePatterns.add(
1✔
790
          RegExpMemberRenamer(
1✔
791
            RegExp(decl, dotAll: true),
1✔
792
            Renamer(
1✔
793
              renameFull: renameFull,
794
              renamePatterns: renamePatterns,
795
            ),
796
          ),
797
        );
798
      }
799
    }
800
  }
801

802
  return Declaration(
29✔
803
    includer: includer,
804
    renamer: Renamer(
29✔
805
      renameFull: renameFull,
806
      renamePatterns: renamePatterns,
807
    ),
808
    memberRenamer: MemberRenamer(
29✔
809
      memberRenameFull: memberRenamerFull,
810
      memberRenamePattern: memberRenamePatterns,
811
    ),
812
    symbolAddressIncluder: symbolIncluder,
813
  );
814
}
815

816
bool declarationConfigValidator(List<String> name, dynamic value) {
29✔
817
  var result = true;
818
  if (value is YamlMap) {
29✔
819
    for (final key in value.keys) {
58✔
820
      if (key == strings.include || key == strings.exclude) {
44✔
821
        if (!checkType<YamlList>([...name, key as String], value[key])) {
100✔
822
          result = false;
823
        }
824
      } else if (key == strings.rename) {
10✔
825
        if (!checkType<YamlMap>([...name, key as String], value[key])) {
8✔
826
          result = false;
827
        } else {
828
          for (final subkey in (value[key] as YamlMap).keys) {
6✔
829
            if (!checkType<String>(
2✔
830
                [...name, key, subkey as String], value[key][subkey])) {
10✔
831
              result = false;
832
            }
833
          }
834
        }
835
      } else if (key == strings.memberRename) {
9✔
836
        if (!checkType<YamlMap>([...name, key as String], value[key])) {
4✔
837
          result = false;
838
        } else {
839
          for (final declNameKey in (value[key] as YamlMap).keys) {
3✔
840
            if (!checkType<YamlMap>([...name, key, declNameKey as String],
4✔
841
                value[key][declNameKey])) {
2✔
842
              result = false;
843
            } else {
844
              for (final memberNameKey
845
                  in ((value[key] as YamlMap)[declNameKey] as YamlMap).keys) {
4✔
846
                if (!checkType<String>([
2✔
847
                  ...name,
848
                  key,
1✔
849
                  declNameKey,
1✔
850
                  memberNameKey as String,
1✔
851
                ], value[key][declNameKey][memberNameKey])) {
3✔
852
                  result = false;
853
                }
854
              }
855
            }
856
          }
857
        }
858
      } else if (key == strings.symbolAddress) {
8✔
859
        if (!checkType<YamlMap>([...name, key as String], value[key])) {
12✔
860
          result = false;
861
        } else {
862
          for (final subkey in (value[key] as YamlMap).keys) {
9✔
863
            if (subkey == strings.include || subkey == strings.exclude) {
3✔
864
              if (!checkType<YamlList>(
3✔
865
                  [...name, key, subkey as String], value[key][subkey])) {
15✔
866
                result = false;
867
              }
868
            } else {
869
              _logger.severe("Unknown key '$subkey' in '$name -> $key'.");
×
870
              result = false;
871
            }
872
          }
873
        }
874
      }
875
    }
876
  } else {
877
    _logger.severe("Expected value '$name' to be a Map.");
×
878
    result = false;
879
  }
880
  return result;
881
}
882

883
Includer exposeFunctionTypeExtractor(dynamic value) =>
1✔
884
    _extractIncluderFromYaml(value);
1✔
885

886
bool exposeFunctionTypeValidator(List<String> name, dynamic value) {
1✔
887
  var result = true;
888

889
  if (!checkType<YamlMap>(name, value)) {
1✔
890
    result = false;
891
  } else {
892
    final mp = value as YamlMap;
893
    for (final key in mp.keys) {
2✔
894
      if (key == strings.include || key == strings.exclude) {
1✔
895
        if (!checkType<YamlList>([...name, key as String], value[key])) {
4✔
896
          result = false;
897
        }
898
      } else {
899
        _logger.severe("Unknown subkey '$key' in '$name'.");
×
900
        result = false;
901
      }
902
    }
903
  }
904

905
  return result;
906
}
907

908
Includer leafFunctionExtractor(dynamic value) =>
1✔
909
    _extractIncluderFromYaml(value);
1✔
910

911
bool leafFunctionValidator(List<String> name, dynamic value) {
1✔
912
  var result = true;
913

914
  if (!checkType<YamlMap>(name, value)) {
1✔
915
    result = false;
916
  } else {
917
    final mp = value as YamlMap;
918
    for (final key in mp.keys) {
2✔
919
      if (key == strings.include || key == strings.exclude) {
1✔
920
        if (!checkType<YamlList>([...name, key as String], value[key])) {
4✔
921
          result = false;
922
        }
923
      } else {
924
        _logger.severe("Unknown subkey '$key' in '$name'.");
×
925
        result = false;
926
      }
927
    }
928
  }
929

930
  return result;
931
}
932

933
SupportedNativeType nativeSupportedType(int value, {bool signed = true}) {
×
934
  switch (value) {
935
    case 1:
×
936
      return signed ? SupportedNativeType.Int8 : SupportedNativeType.Uint8;
937
    case 2:
×
938
      return signed ? SupportedNativeType.Int16 : SupportedNativeType.Uint16;
939
    case 4:
×
940
      return signed ? SupportedNativeType.Int32 : SupportedNativeType.Uint32;
941
    case 8:
×
942
      return signed ? SupportedNativeType.Int64 : SupportedNativeType.Uint64;
943
    default:
944
      throw Exception(
×
945
          'Unsupported value given to sizemap, Allowed values for sizes are: 1, 2, 4, 8');
946
  }
947
}
948

949
String stringExtractor(dynamic value) => value as String;
48✔
950

951
bool nonEmptyStringValidator(List<String> name, dynamic value) {
47✔
952
  if (value is String && value.isNotEmpty) {
94✔
953
    return true;
954
  } else {
955
    _logger.severe("Expected value of key '$name' to be a non-empty String.");
×
956
    return false;
957
  }
958
}
959

960
bool dartClassNameValidator(List<String> name, dynamic value) {
48✔
961
  if (value is String &&
48✔
962
      quiver.matchesFull(RegExp('[a-zA-Z]+[_a-zA-Z0-9]*'), value)) {
96✔
963
    return true;
964
  } else {
965
    _logger.severe(
×
966
        "Expected value of key '$name' to be a valid public class name.");
×
967
    return false;
968
  }
969
}
970

971
CommentType commentExtractor(dynamic value) {
4✔
972
  if (value is bool) {
4✔
973
    if (value) {
974
      return CommentType.def();
×
975
    } else {
976
      return CommentType.none();
1✔
977
    }
978
  }
979
  final ct = CommentType.def();
3✔
980
  if (value is YamlMap) {
3✔
981
    for (final key in value.keys) {
6✔
982
      if (key == strings.style) {
3✔
983
        if (value[key] == strings.any) {
6✔
984
          ct.style = CommentStyle.any;
2✔
985
        } else if (value[key] == strings.doxygen) {
4✔
986
          ct.style = CommentStyle.doxygen;
2✔
987
        }
988
      } else if (key == strings.length) {
3✔
989
        if (value[key] == strings.full) {
6✔
990
          ct.length = CommentLength.full;
3✔
991
        } else if (value[key] == strings.brief) {
2✔
992
          ct.length = CommentLength.brief;
1✔
993
        }
994
      }
995
    }
996
  }
997
  return ct;
998
}
999

1000
bool commentValidator(List<String> name, dynamic value) {
4✔
1001
  if (value is bool) {
4✔
1002
    return true;
1003
  } else if (value is YamlMap) {
3✔
1004
    var result = true;
1005
    for (final key in value.keys) {
6✔
1006
      if (key == strings.style) {
3✔
1007
        if (value[key] is! String ||
6✔
1008
            !(value[key] == strings.doxygen || value[key] == strings.any)) {
10✔
1009
          _logger.severe(
×
1010
              "'$name'>'${strings.style}' must be one of the following - {${strings.doxygen}, ${strings.any}}");
×
1011
          result = false;
1012
        }
1013
      } else if (key == strings.length) {
3✔
1014
        if (value[key] is! String ||
6✔
1015
            !(value[key] == strings.brief || value[key] == strings.full)) {
12✔
1016
          _logger.severe(
×
1017
              "'$name'>'${strings.length}' must be one of the following - {${strings.brief}, ${strings.full}}");
×
1018
          result = false;
1019
        }
1020
      } else {
1021
        _logger.severe("Unknown key '$key' in '$name'.");
×
1022
        result = false;
1023
      }
1024
    }
1025
    return result;
1026
  } else {
1027
    _logger.severe("Expected value of key '$name' to be a bool or a Map.");
×
1028
    return false;
1029
  }
1030
}
1031

1032
CompoundDependencies dependencyOnlyExtractor(dynamic value) {
1✔
1033
  var result = CompoundDependencies.full;
1034
  if (value == strings.opaqueCompoundDependencies) {
1✔
1035
    result = CompoundDependencies.opaque;
1036
  }
1037
  return result;
1038
}
1039

1040
bool dependencyOnlyValidator(List<String> name, dynamic value) {
1✔
1041
  var result = true;
1042
  if (value is! String ||
1✔
1043
      !(value == strings.fullCompoundDependencies ||
1✔
1044
          value == strings.opaqueCompoundDependencies)) {
1✔
1045
    _logger.severe(
×
1046
        "'$name' must be one of the following - {${strings.fullCompoundDependencies}, ${strings.opaqueCompoundDependencies}}");
×
1047
    result = false;
1048
  }
1049
  return result;
1050
}
1051

1052
StructPackingOverride structPackingOverrideExtractor(dynamic value) {
1✔
1053
  final matcherMap = <RegExp, int?>{};
1✔
1054
  for (final key in (value as YamlMap).keys) {
2✔
1055
    matcherMap[RegExp(key as String, dotAll: true)] =
2✔
1056
        strings.packingValuesMap[value[key]];
2✔
1057
  }
1058
  return StructPackingOverride(matcherMap: matcherMap);
1✔
1059
}
1060

1061
bool structPackingOverrideValidator(List<String> name, dynamic value) {
1✔
1062
  var result = true;
1063

1064
  if (!checkType<YamlMap>([...name], value)) {
2✔
1065
    result = false;
1066
  } else {
1067
    for (final key in (value as YamlMap).keys) {
2✔
1068
      if (!(strings.packingValuesMap.keys.contains(value[key]))) {
3✔
1069
        _logger.severe(
2✔
1070
            "'$name -> $key' must be one of the following - ${strings.packingValuesMap.keys.toList()}");
3✔
1071
        result = false;
1072
      }
1073
    }
1074
  }
1075

1076
  return result;
1077
}
1078

1079
FfiNativeConfig ffiNativeExtractor(dynamic yamlConfig) {
1✔
1080
  final yamlMap = yamlConfig as YamlMap?;
1081
  return FfiNativeConfig(
1✔
1082
    enabled: true,
1083
    asset: yamlMap?[strings.ffiNativeAsset] as String?,
×
1084
  );
1085
}
1086

1087
bool ffiNativeValidator(List<String> name, dynamic yamlConfig) {
1✔
1088
  if (!checkType<YamlMap?>(name, yamlConfig)) {
1✔
1089
    return false;
1090
  }
1091
  if (yamlConfig == null) {
1092
    // Empty means no asset name.
1093
    return true;
1094
  }
1095
  for (final key in (yamlConfig as YamlMap).keys) {
×
1096
    if (!checkType<String>([...name, key as String], yamlConfig[key])) {
×
1097
      return false;
1098
    }
1099
    if (key != strings.ffiNativeAsset) {
×
1100
      _logger.severe("'$name -> $key' must be one of the following - ${[
×
1101
        strings.ffiNativeAsset
1102
      ]}");
×
1103
    }
1104
  }
1105
  return true;
1106
}
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