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

dart-lang / ffigen / 4428795187

pending completion
4428795187

Pull #534

github

GitHub
Merge e0c7e1bbb into 92dc22649
Pull Request #534: Detect LLVM installation using scoop

4 of 4 new or added lines in 1 file covered. (100.0%)

3190 of 3457 relevant lines covered (92.28%)

24.69 hits per line

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

74.64
/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
    final dylibLocations = strings.windowsDylibLocations.toList();
×
499
    final userHome = Platform.environment['USERPROFILE'];
×
500
    if (userHome != null) {
501
      dylibLocations
502
          .add(p.join(userHome, 'scoop', 'apps', 'llvm', 'current', 'bin'));
×
503
    }
504
    for (final l in dylibLocations) {
×
505
      k = findLibclangDylib(l);
×
506
      if (k != null) return k;
507
    }
508
  } else if (Platform.isMacOS) {
48✔
509
    for (final l in strings.macOsDylibLocations) {
96✔
510
      k = findLibclangDylib(l);
48✔
511
      if (k != null) return k;
512
    }
513
    final findLibraryResult =
514
        Process.runSync('xcodebuild', ['-find-library', 'libclang.dylib']);
×
515
    if (findLibraryResult.exitCode == 0) {
×
516
      final location = (findLibraryResult.stdout as String).split('\n').first;
×
517
      if (File(location).existsSync()) {
×
518
        return location;
519
      }
520
    }
521
    final xcodePathResult = Process.runSync('xcode-select', ['-print-path']);
×
522
    if (xcodePathResult.exitCode == 0) {
×
523
      final xcodePath = (xcodePathResult.stdout as String).split('\n').first;
×
524
      final location =
525
          p.join(xcodePath, strings.xcodeDylibLocation, strings.dylibFileName);
×
526
      if (File(location).existsSync()) {
×
527
        return location;
528
      }
529
    }
530
  } else {
531
    throw Exception('Unsupported Platform.');
×
532
  }
533

534
  _logger.severe("Couldn't find dynamic library in default locations.");
×
535
  _logger.severe(
×
536
      "Please supply one or more path/to/llvm in ffigen's config under the key '${strings.llvmPath}'.");
×
537
  throw Exception("Couldn't find dynamic library in default locations.");
×
538
}
539

540
String? findLibclangDylib(String parentFolder) {
48✔
541
  final location = p.join(parentFolder, strings.dylibFileName);
96✔
542
  if (File(location).existsSync()) {
96✔
543
    return location;
544
  } else {
545
    return null;
546
  }
547
}
548

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

581
bool llvmPathValidator(List<String> name, dynamic value) {
×
582
  if (!checkType<YamlList>(name, value)) {
×
583
    return false;
584
  }
585
  return true;
586
}
587

588
OutputConfig outputExtractor(
48✔
589
    dynamic value, String? configFilename, PackageConfig? packageConfig) {
590
  if (value is String) {
48✔
591
    return OutputConfig(_normalizePath(value, configFilename), null);
96✔
592
  }
593
  value = value as YamlMap;
594
  return OutputConfig(
1✔
595
    _normalizePath((value)[strings.bindings] as String, configFilename),
2✔
596
    value.containsKey(strings.symbolFile)
1✔
597
        ? symbolFileOutputExtractor(
1✔
598
            value[strings.symbolFile], configFilename, packageConfig)
1✔
599
        : null,
600
  );
601
}
602

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

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

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

674
Language languageExtractor(dynamic value) {
17✔
675
  if (value == strings.langC) {
17✔
676
    return Language.c;
677
  } else if (value == strings.langObjC) {
17✔
678
    return Language.objc;
679
  }
680
  return Language.c;
681
}
682

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

701
/// Returns true if [str] is not a full name.
702
///
703
/// E.g `abc` is a full name, `abc.*` is not.
704
bool isFullDeclarationName(String str) =>
27✔
705
    quiver.matchesFull(RegExp('[a-zA-Z_0-9]*'), str);
54✔
706

707
Includer _extractIncluderFromYaml(dynamic yamlMap) {
29✔
708
  final includeMatchers = <RegExp>[],
29✔
709
      includeFull = <String>{},
710
      excludeMatchers = <RegExp>[],
29✔
711
      excludeFull = <String>{};
712

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

727
  final exclude = (yamlMap[strings.exclude] as YamlList?)?.cast<String>();
35✔
728
  if (exclude != null) {
729
    for (final str in exclude) {
12✔
730
      if (isFullDeclarationName(str)) {
6✔
731
        excludeFull.add(str);
6✔
732
      } else {
733
        excludeMatchers.add(RegExp(str, dotAll: true));
×
734
      }
735
    }
736
  }
737

738
  return Includer(
29✔
739
    includeMatchers: includeMatchers,
740
    includeFull: includeFull,
741
    excludeMatchers: excludeMatchers,
742
    excludeFull: excludeFull,
743
  );
744
}
745

746
Declaration declarationConfigExtractor(dynamic yamlMap) {
29✔
747
  final renamePatterns = <RegExpRenamer>[];
29✔
748
  final renameFull = <String, String>{};
29✔
749
  final memberRenamePatterns = <RegExpMemberRenamer>[];
29✔
750
  final memberRenamerFull = <String, Renamer>{};
29✔
751

752
  final includer = _extractIncluderFromYaml(yamlMap);
29✔
753

754
  Includer? symbolIncluder;
755
  if (yamlMap[strings.symbolAddress] != null) {
29✔
756
    symbolIncluder = _extractIncluderFromYaml(yamlMap[strings.symbolAddress]);
6✔
757
  }
758

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

761
  if (rename != null) {
762
    for (final str in rename.keys) {
4✔
763
      if (isFullDeclarationName(str)) {
2✔
764
        renameFull[str] = rename[str]!;
2✔
765
      } else {
766
        renamePatterns
767
            .add(RegExpRenamer(RegExp(str, dotAll: true), rename[str]!));
8✔
768
      }
769
    }
770
  }
771

772
  final memberRename =
773
      (yamlMap[strings.memberRename] as YamlMap?)?.cast<String, YamlMap>();
30✔
774

775
  if (memberRename != null) {
776
    for (final decl in memberRename.keys) {
2✔
777
      final renamePatterns = <RegExpRenamer>[];
1✔
778
      final renameFull = <String, String>{};
1✔
779

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

808
  return Declaration(
29✔
809
    includer: includer,
810
    renamer: Renamer(
29✔
811
      renameFull: renameFull,
812
      renamePatterns: renamePatterns,
813
    ),
814
    memberRenamer: MemberRenamer(
29✔
815
      memberRenameFull: memberRenamerFull,
816
      memberRenamePattern: memberRenamePatterns,
817
    ),
818
    symbolAddressIncluder: symbolIncluder,
819
  );
820
}
821

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

889
Includer exposeFunctionTypeExtractor(dynamic value) =>
1✔
890
    _extractIncluderFromYaml(value);
1✔
891

892
bool exposeFunctionTypeValidator(List<String> name, dynamic value) {
1✔
893
  var result = true;
894

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

911
  return result;
912
}
913

914
Includer leafFunctionExtractor(dynamic value) =>
1✔
915
    _extractIncluderFromYaml(value);
1✔
916

917
bool leafFunctionValidator(List<String> name, dynamic value) {
1✔
918
  var result = true;
919

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

936
  return result;
937
}
938

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

955
String stringExtractor(dynamic value) => value as String;
48✔
956

957
bool nonEmptyStringValidator(List<String> name, dynamic value) {
47✔
958
  if (value is String && value.isNotEmpty) {
94✔
959
    return true;
960
  } else {
961
    _logger.severe("Expected value of key '$name' to be a non-empty String.");
×
962
    return false;
963
  }
964
}
965

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

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

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

1038
CompoundDependencies dependencyOnlyExtractor(dynamic value) {
1✔
1039
  var result = CompoundDependencies.full;
1040
  if (value == strings.opaqueCompoundDependencies) {
1✔
1041
    result = CompoundDependencies.opaque;
1042
  }
1043
  return result;
1044
}
1045

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

1058
StructPackingOverride structPackingOverrideExtractor(dynamic value) {
1✔
1059
  final matcherMap = <RegExp, int?>{};
1✔
1060
  for (final key in (value as YamlMap).keys) {
2✔
1061
    matcherMap[RegExp(key as String, dotAll: true)] =
2✔
1062
        strings.packingValuesMap[value[key]];
2✔
1063
  }
1064
  return StructPackingOverride(matcherMap: matcherMap);
1✔
1065
}
1066

1067
bool structPackingOverrideValidator(List<String> name, dynamic value) {
1✔
1068
  var result = true;
1069

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

1082
  return result;
1083
}
1084

1085
FfiNativeConfig ffiNativeExtractor(dynamic yamlConfig) {
1✔
1086
  final yamlMap = yamlConfig as YamlMap?;
1087
  return FfiNativeConfig(
1✔
1088
    enabled: true,
1089
    asset: yamlMap?[strings.ffiNativeAsset] as String?,
×
1090
  );
1091
}
1092

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