checkReferences static method

void checkReferences(
  1. Map<String, Script> scripts, {
  2. required String file,
})

Checks that every run_hooks_from and depends_on name refers to a script in scripts, and that depends_on has no cycles. Run after the root and package scripts are merged, since either may refer to the other.

Implementation

static void checkReferences(
  Map<String, Script> scripts, {
  required String file,
}) {
  ConfigException missing(Script script, String key, String name) {
    final suggestion = closestMatch(name, scripts.keys);
    return ConfigException(
      'scripts.${script.name}.$key: no script named "$name".'
      '${suggestion == null ? '' : ' Did you mean "$suggestion"?'}',
      file: file,
    );
  }

  for (final script in scripts.values) {
    final from = script.runHooksFrom;
    if (from != null && !scripts.containsKey(from) && !_isKnownTarget(from)) {
      throw missing(script, 'run_hooks_from', from);
    }
    for (final dependency in script.dependsOn) {
      final name = dependency.startsWith('^')
          ? dependency.substring(1)
          : dependency;
      final target = scripts[name];
      if (target == null) {
        throw missing(script, 'depends_on', name);
      }
      if (target.isHook) {
        throw ConfigException(
          'scripts.${script.name}.depends_on: "$name" is a hook. Hooks run '
          'around their target; depend on a script instead.',
          file: file,
        );
      }
    }
  }

  final visiting = <String>{};
  final done = <String>{};
  void visit(String name, List<String> path) {
    if (done.contains(name)) {
      return;
    }
    if (!visiting.add(name)) {
      final cycle = [...path.sublist(path.indexOf(name)), name];
      throw ConfigException(
        'scripts: depends_on forms a cycle: ${cycle.join(' -> ')}.',
        file: file,
      );
    }
    for (final dependency in scripts[name]?.dependsOn ?? const <String>[]) {
      if (!dependency.startsWith('^')) {
        visit(dependency, [...path, name]);
      }
    }
    visiting.remove(name);
    done.add(name);
  }

  for (final name in scripts.keys) {
    visit(name, const []);
  }
}