content: // Copyright 2016 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:source_span/source_span.dart';
  
  import '../ast/css.dart';
  import '../ast/selector.dart';
  import '../exception.dart';
  import '../utils.dart';
  
  /// The state of an extension for a given extender.
  ///
  /// The target of the extension is represented externally, in the map that
  /// contains this extender.
  class Extension {
    /// The extender (such as `A` in `A {@extend B}`).
    final Extender extender;
  
    /// The selector that's being extended.
    final SimpleSelector target;
  
    /// The media query context to which this extension is restricted, or `null`
    /// if it can apply within any context.
    final List<CssMediaQuery>? mediaContext;
  
    /// Whether this extension is optional.
    final bool isOptional;
  
    /// The span for an `@extend` rule that defined this extension.
    ///
    /// If any extend rule for this is extension is mandatory, this is guaranteed
    /// to be a span for a mandatory rule.
    final FileSpan span;
  
    /// Creates a new extension.
    Extension(
      ComplexSelector extender,
      this.target,
      this.span, {
      this.mediaContext,
      bool optional = false,
    })  : extender = Extender(extender),
          isOptional = optional {
      this.extender._extension = this;
    }
  
    Extension withExtender(ComplexSelector newExtender) => Extension(
          newExtender,
          target,
          span,
          mediaContext: mediaContext,
          optional: isOptional,
        );
  
    String toString() =>
        "$extender {@extend $target${isOptional ? ' !optional' : ''}}";
  }
  
  /// A selector that's extending another selector, such as `A` in `A {@extend
  /// B}`.
  final class Extender {
    /// The selector in which the `@extend` appeared.
    final ComplexSelector selector;
  
    /// The minimum specificity required for any selector generated from this
    /// extender.
    final int specificity;
  
    /// Whether this extender represents a selector that was originally in the
    /// document, rather than one defined with `@extend`.
    final bool isOriginal;
  
    /// The extension that created this [Extender].
    ///
    /// Not all [Extender]s are created by extensions. Some simply represent the
    /// original selectors that exist in the document.
    Extension? _extension;
  
    /// Creates a new extender.
    ///
    /// If [specificity] isn't passed, it defaults to `extender.specificity`.
    Extender(this.selector, {int? specificity, bool original = false})
        : specificity = specificity ?? selector.specificity,
          isOriginal = original;
  
    /// Asserts that the [mediaContext] for a selector is compatible with the
    /// query context for this extender.
    void assertCompatibleMediaContext(List<CssMediaQuery>? mediaContext) {
      var extension = _extension;
      if (extension == null) return;
  
      var expectedMediaContext = extension.mediaContext;
      if (expectedMediaContext == null) return;
      if (mediaContext != null &&
          listEquals(expectedMediaContext, mediaContext)) {
        return;
      }
  
      throw SassException(
        "You may not @extend selectors across media queries.",
        extension.span,
      );
    }
  
    String toString() => selector.toString();
  }
  