content: // Copyright 2018 Google Inc. Use of this source code is governed by an
  // MIT-style license that can be found in the LICENSE file or at
  // https://opensource.org/licenses/MIT.
  
  import 'package:collection/collection.dart';
  
  import '../ast/sass.dart';
  import 'recursive_statement.dart';
  
  /// Returns [stylesheet]'s statically-declared dependencies.
  ///
  /// {@category Dependencies}
  DependencyReport findDependencies(Stylesheet stylesheet) =>
      _FindDependenciesVisitor().run(stylesheet);
  
  /// A visitor that traverses a stylesheet and records all its dependencies on
  /// other stylesheets.
  class _FindDependenciesVisitor with RecursiveStatementVisitor {
    final _uses = <Uri>{};
    final _forwards = <Uri>{};
    final _metaLoadCss = <Uri>{};
    final _imports = <Uri>{};
  
    /// The namespaces under which `sass:meta` has been `@use`d in this stylesheet.
    ///
    /// If this contains `null`, it means `sass:meta` was loaded without a
    /// namespace.
    final _metaNamespaces = <String?>{};
  
    DependencyReport run(Stylesheet stylesheet) {
      visitStylesheet(stylesheet);
      return DependencyReport._(
        uses: UnmodifiableSetView(_uses),
        forwards: UnmodifiableSetView(_forwards),
        metaLoadCss: UnmodifiableSetView(_metaLoadCss),
        imports: UnmodifiableSetView(_imports),
      );
    }
  
    // These can never contain imports.
    void visitEachRule(EachRule node) {}
    void visitForRule(ForRule node) {}
    void visitIfRule(IfRule node) {}
    void visitWhileRule(WhileRule node) {}
    void visitCallableDeclaration(CallableDeclaration node) {}
    void visitInterpolation(Interpolation interpolation) {}
    void visitSupportsCondition(SupportsCondition condition) {}
  
    void visitUseRule(UseRule node) {
      if (node.url.scheme != 'sass') {
        _uses.add(node.url);
      } else if (node.url.toString() == 'sass:meta') {
        _metaNamespaces.add(node.namespace);
      }
    }
  
    void visitForwardRule(ForwardRule node) {
      if (node.url.scheme != 'sass') _forwards.add(node.url);
    }
  
    void visitImportRule(ImportRule node) {
      for (var import in node.imports) {
        if (import is DynamicImport) _imports.add(import.url);
      }
    }
  
    void visitIncludeRule(IncludeRule node) {
      if (node.name != 'load-css') return;
      if (!_metaNamespaces.contains(node.namespace)) return;
  
      if (node.arguments.positional
          case [
            StringExpression(text: Interpolation(asPlain: var url?)),
          ]) {
        try {
          _metaLoadCss.add(Uri.parse(url));
        } on FormatException {
          // Ignore invalid URLs.
        }
      }
    }
  }
  
  /// A struct of different types of dependencies a Sass stylesheet can contain.
  final class DependencyReport {
    /// An unmodifiable set of all `@use`d URLs in the stylesheet (excluding
    /// built-in modules).
    final Set<Uri> uses;
  
    /// An unmodifiable set of all `@forward`ed URLs in the stylesheet (excluding
    /// built-in modules).
    final Set<Uri> forwards;
  
    /// An unmodifiable set of all URLs loaded by `meta.load-css()` calls with
    /// static string arguments outside of mixins.
    final Set<Uri> metaLoadCss;
  
    /// An unmodifiable set of all dynamically `@import`ed URLs in the
    /// stylesheet.
    final Set<Uri> imports;
  
    /// An unmodifiable set of all URLs in [uses], [forwards], and [metaLoadCss].
    Set<Uri> get modules => UnionSet({uses, forwards, metaLoadCss});
  
    /// An unmodifiable set of all URLs in [uses], [forwards], [metaLoadCss], and
    /// [imports].
    Set<Uri> get all => UnionSet({uses, forwards, metaLoadCss, imports});
  
    DependencyReport._({
      required this.uses,
      required this.forwards,
      required this.metaLoadCss,
      required this.imports,
    });
  }
  