From 3fb3ae1f3bf42ba8f3facaf3adbee4d997a11b40 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Sat, 3 Sep 2016 03:29:01 -0700 Subject: [PATCH] Throw if an extend doesn't match. --- lib/src/extend/extender.dart | 41 ++++++++++++++++++++++++------------ lib/src/extend/source.dart | 22 +++++++++++++++++++ lib/src/visitor/perform.dart | 3 ++- 3 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 lib/src/extend/source.dart diff --git a/lib/src/extend/extender.dart b/lib/src/extend/extender.dart index 073a7230..2049fe4b 100644 --- a/lib/src/extend/extender.dart +++ b/lib/src/extend/extender.dart @@ -11,6 +11,7 @@ import 'package:source_span/source_span.dart'; import '../ast/css.dart'; import '../ast/selector.dart'; import '../utils.dart'; +import 'source.dart'; import 'functions.dart'; class Extender { @@ -20,7 +21,7 @@ class Extender { /// This is used to find which rules an `@extend` applies to. final _selectors = >{}; - final _extensions = >{}; + final _extensions = >{}; final _sources = new Expando(); @@ -53,23 +54,33 @@ class Extender { return rule; } - void addExtension(SelectorList extender, SimpleSelector target) { - _extensions.putIfAbsent(target, () => new Set()).add(extender); + void addExtension( + SelectorList sourceList, SimpleSelector target, FileSpan span) { + var source = new ExtendSource(sourceList, span); + _extensions.putIfAbsent(target, () => new Set()).add(source); var rules = _selectors[target]; if (rules == null) return; - var extensions = { - target: new Set.from([extender]) - }; + var extensions = {target: new Set()..add(source)}; for (var rule in rules) { var list = rule.selector.value; rule.selector.value = _extendList(list, extensions); } } + void finalize() { + for (var sources in _extensions.values) { + for (var source in sources) { + if (source.isUsed) continue; + throw source.span.message('The target selector was not found.\n' + 'Use "@extend %foo !optional" to avoid this error.'); + } + } + } + SelectorList _extendList( - SelectorList list, Map> extensions) { + SelectorList list, Map> extensions) { // This could be written more simply using [List.map], but we want to avoid // any allocations in the common case where no extends apply. var changed = false; @@ -92,7 +103,7 @@ class Extender { } Iterable _extendComplex(ComplexSelector complex, - Map> extensions) { + Map> extensions) { // This could be written more simply using [List.map], but we want to avoid // any allocations in the common case where no extends apply. var changed = false; @@ -136,7 +147,7 @@ class Extender { List> _extendCompound( CompoundSelector compound, - Map> extensions) { + Map> extensions) { var changed = false; List> extended; for (var i = 0; i < compound.components.length; i++) { @@ -144,26 +155,28 @@ class Extender { // TODO: handle extending into pseudo selectors, extend failures - var extenders = extensions[simple]; - if (extenders == null) continue; + var sources = extensions[simple]; + if (sources == null) continue; var compoundWithoutSimple = compound.components.toList()..removeAt(i); - for (var list in extenders) { - for (var complex in list.components) { + for (var source in sources) { + for (var complex in source.extender.components) { var extenderBase = complex.components.last as CompoundSelector; var unified = compoundWithoutSimple.isEmpty ? extenderBase : _unifyCompound(extenderBase.components, compoundWithoutSimple); if (unified == null) continue; - if (!changed) + if (!changed) { extended = [ [compound] ]; + } changed = true; extended.add(complex.components .take(complex.components.length - 1) .toList()..add(unified)); + source.isUsed = true; } } } diff --git a/lib/src/extend/source.dart b/lib/src/extend/source.dart new file mode 100644 index 00000000..e77bef3e --- /dev/null +++ b/lib/src/extend/source.dart @@ -0,0 +1,22 @@ +// 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/selector.dart'; + +class ExtendSource { + final SelectorList extender; + + final FileSpan span; + + bool isUsed = false; + + ExtendSource(this.extender, this.span); + + int get hashCode => extender.hashCode; + + bool operator ==(Object other) => + other is ExtendSource && other.extender == extender; +} diff --git a/lib/src/visitor/perform.dart b/lib/src/visitor/perform.dart index 17913796..d24a57cd 100644 --- a/lib/src/visitor/perform.dart +++ b/lib/src/visitor/perform.dart @@ -68,6 +68,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor { for (var child in node.children) { child.accept(this); } + _extender.finalize(); return _root; } @@ -206,7 +207,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor { // TODO: recontextualize parse errors. // TODO: disallow parent selectors. var simple = new Parser(targetText.value).parseSimpleSelector(); - _extender.addExtension(_selector.value, simple); + _extender.addExtension(_selector.value, simple, node.span); } void visitAtRule(AtRule node) {