diff --git a/CHANGELOG.md b/CHANGELOG.md index c75fcaac..1696968b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ [import-only files]: https://sass-lang.com/documentation/at-rules/import#import-only-files +### Dart API + +* Add an `Importer.fromImport` getter, which is `true` if the current + `Importer.canonicalize()` call comes from an `@import` rule and `false` + otherwise. Importers should only use this to determine whether to load + [import-only files]. + ## 1.32.13 * **Potentially breaking bug fix:** Null values in `@use` and `@forward` diff --git a/lib/src/importer/async.dart b/lib/src/importer/async.dart index 3d2591ba..cc61c8fb 100644 --- a/lib/src/importer/async.dart +++ b/lib/src/importer/async.dart @@ -4,7 +4,10 @@ import 'dart:async'; +import 'package:meta/meta.dart'; + import 'result.dart'; +import 'utils.dart' as utils; /// An interface for importers that resolves URLs in `@import`s to the contents /// of Sass files. @@ -20,6 +23,22 @@ import 'result.dart'; /// /// Subclasses should extend [AsyncImporter], not implement it. abstract class AsyncImporter { + /// Whether the current [canonicalize] invocation comes from an `@import` + /// rule. + /// + /// When evaluating `@import` rules, URLs should canonicalize to an + /// [import-only file] if one exists for the URL being canonicalized. + /// Otherwise, canonicalization should be identical for `@import` and `@use` + /// rules. + /// + /// [import-only file]: https://sass-lang.com/documentation/at-rules/import#import-only-files + /// + /// Subclasses should only access this from within calls to [canonicalize]. + /// Outside of that context, its value is undefined and subject to change. + @protected + @nonVirtual + bool get fromImport => utils.fromImport; + /// If [url] is recognized by this importer, returns its canonical format. /// /// Note that canonical URLs *must* be absolute, including a scheme. Returning diff --git a/lib/src/importer/utils.dart b/lib/src/importer/utils.dart index 6e5d51fe..b7eac3ac 100644 --- a/lib/src/importer/utils.dart +++ b/lib/src/importer/utils.dart @@ -2,6 +2,8 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'dart:async'; + import 'package:path/path.dart' as p; import '../io.dart'; @@ -13,30 +15,12 @@ import '../io.dart'; /// canonicalization should be identical for `@import` and `@use` rules. It's /// admittedly hacky to set this globally, but `@import` will eventually be /// removed, at which point we can delete this and have one consistent behavior. -bool _inImportRule = false; +bool get fromImport => Zone.current[#_inImportRule] as bool? ?? false; -/// Runs [callback] in a context where [resolveImportPath] uses `@import` -/// semantics rather than `@use` semantics. -T inImportRule(T callback()) { - var wasInImportRule = _inImportRule; - _inImportRule = true; - try { - return callback(); - } finally { - _inImportRule = wasInImportRule; - } -} - -/// Like [inImportRule], but asynchronous. -Future inImportRuleAsync(Future callback()) async { - var wasInImportRule = _inImportRule; - _inImportRule = true; - try { - return await callback(); - } finally { - _inImportRule = wasInImportRule; - } -} +/// Runs [callback] in a context where [inImportRule] returns `true` and +/// [resolveImportPath] uses `@import` semantics rather than `@use` semantics. +T inImportRule(T callback()) => + runZoned(callback, zoneValues: {#_inImportRule: true}); /// Resolves an imported path using the same logic as the filesystem importer. /// @@ -95,7 +79,7 @@ String? _exactlyOne(List paths) { paths.map((path) => " " + p.prettyUri(p.toUri(path))).join("\n"); } -/// If [_inImportRule] is `true`, invokes callback and returns the result. +/// If [fromImport] is `true`, invokes callback and returns the result. /// /// Otherwise, returns `null`. -T? _ifInImport(T callback()) => _inImportRule ? callback() : null; +T? _ifInImport(T callback()) => fromImport ? callback() : null; diff --git a/test/dart_api/from_import_importer.dart b/test/dart_api/from_import_importer.dart new file mode 100644 index 00000000..bfab408a --- /dev/null +++ b/test/dart_api/from_import_importer.dart @@ -0,0 +1,28 @@ +// Copyright 2017 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:sass/sass.dart'; +import 'package:test/test.dart'; + +/// An [Importer] whose [canonicalize] method asserts the value of +/// [Importer.fromImport]. +class FromImportImporter extends Importer { + /// The expected value of [Importer.fromImport] in the call to [canonicalize]. + final bool _expected; + + /// The callback to call once [canonicalize] is called. + /// + /// This ensures that the test doesn't exit until [canonicalize] is called. + final void Function() _done; + + FromImportImporter(this._expected) : _done = expectAsync0(() {}); + + Uri? canonicalize(Uri url) { + expect(fromImport, equals(_expected)); + _done(); + return Uri.parse('u:'); + } + + ImporterResult? load(Uri url) => ImporterResult("", syntax: Syntax.scss); +} diff --git a/test/dart_api/importer_test.dart b/test/dart_api/importer_test.dart index 7ffae8f9..430ec00e 100644 --- a/test/dart_api/importer_test.dart +++ b/test/dart_api/importer_test.dart @@ -12,6 +12,7 @@ import 'package:test/test.dart'; import 'package:sass/sass.dart'; import 'package:sass/src/exception.dart'; +import 'from_import_importer.dart'; import 'test_importer.dart'; import '../utils.dart'; @@ -182,4 +183,23 @@ void main() { return true; }))); }); + + group("currentLoadFromImport is", () { + test("true from an @import", () { + compileString('@import "foo"', importers: [FromImportImporter(true)]); + }); + + test("false from a @use", () { + compileString('@use "foo"', importers: [FromImportImporter(false)]); + }); + + test("false from a @forward", () { + compileString('@forward "foo"', importers: [FromImportImporter(false)]); + }); + + test("false from meta.load-css", () { + compileString('@use "sass:meta"; @include meta.load-css("")', + importers: [FromImportImporter(false)]); + }); + }); }