From d276bfb206695f3f036fd1d8f12d0203c1ef21b2 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Sat, 15 Oct 2016 16:21:04 -0700 Subject: [PATCH] Finish API docs. --- lib/src/visitor/perform.dart | 90 +++++++++++++++++++++++++++++++++- lib/src/visitor/serialize.dart | 57 +++++++++++++++++++-- 2 files changed, 142 insertions(+), 5 deletions(-) diff --git a/lib/src/visitor/perform.dart b/lib/src/visitor/perform.dart index 5802dea6..9b1baa04 100644 --- a/lib/src/visitor/perform.dart +++ b/lib/src/visitor/perform.dart @@ -145,13 +145,23 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { } if (outerCopy != null) root.addChild(outerCopy); - _scopeForAtRule(innerCopy ?? root, query)(() { + _scopeForAtRoot(innerCopy ?? root, query)(() { for (var child in node.children) { child.accept(this); } }); } + /// Destructively trims a trailing sublist that matches the current list of + /// parents from [nodes]. + /// + /// [nodes] should be a list of parents included by an `@at-root` rule, from + /// innermost to outermost. If it contains a trailing sublist that's + /// contiguous—meaning that each node is a direct parent of the node before + /// it—and whose final node is a direct child of [_root], this removes that + /// sublist and returns the innermost removed parent. + /// + /// Otherwise, this leaves [nodes] as-is and returns [_root]. CssParentNode _trimIncluded(List nodes) { var parent = _parent; int innermostContiguous; @@ -171,7 +181,12 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { return root; } - _ScopeCallback _scopeForAtRule(CssNode newParent, AtRootQuery query) { + /// Returns a [_ScopeCallback] for [query]. + /// + /// This returns a callback that adjusts various instance variables for its + /// duration, based on which rules are excluded by [query]. It always assigns + /// [_parent] to [newParent]. + _ScopeCallback _scopeForAtRoot(CssNode newParent, AtRootQuery query) { var scope = (callback()) { // We can't use [_withParent] here because it'll add the node to the tree // in the wrong place. @@ -260,6 +275,8 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { }, semiGlobal: true); } + /// Destructures [value] and assigns it to [variables], as in an `@each` + /// statement. void _setMultipleVariables(List variables, Value value) { var list = value.asList; var minLength = math.min(variables.length, list.length); @@ -377,6 +394,8 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { }); } + /// Loads the [Stylesheet] imported by [node], or throws a + /// [SassRuntimeException] if loading fails. Stylesheet _loadImport(ImportRule node) { var path = _importPaths.putIfAbsent(node, () { var path = p.fromUri(node.url); @@ -408,9 +427,14 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { }); } + /// Like [_tryImportPath], but checks both `.sass` and `.scss` extensions. String _tryImportPathWithExtensions(String path) => _tryImportPath(path + '.sass') ?? _tryImportPath(path + '.scss'); + /// If a file exists at [path], or a partial with the same name exists, + /// returns the resolved path. + /// + /// Otherwise, returns `null`. String _tryImportPath(String path) { var partial = p.join(p.dirname(path), "_${p.basename(path)}"); if (fileExists(partial)) return partial; @@ -484,6 +508,8 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { }, through: (node) => node is CssStyleRule || node is CssMediaRule); } + /// Returns a list of queries that selects for platforms that match both + /// [queries1] and [queries2]. List _mergeMediaQueries( Iterable queries1, Iterable queries2) { return new List.unmodifiable(queries1.expand/**/((query1) { @@ -491,6 +517,7 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { }).where((query) => query != null)); } + /// Evaluates [query] and converts it to a plain CSS query. CssMediaQuery _visitMediaQuery(MediaQuery query) { var modifier = query.modifier == null ? null : _interpolationToValue(query.modifier); @@ -564,6 +591,7 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { }, through: (node) => node is CssStyleRule); } + /// Evaluates [condition] and converts it to a plain CSS string. String _visitSupportsCondition(SupportsCondition condition) { if (condition is SupportsOperation) { return "${_parenthesize(condition.left, condition.operator)} " @@ -581,6 +609,12 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { } } + /// Evlauates [condition] and converts it to a plain CSS string, with + /// parentheses if necessary. + /// + /// If [operator] is passed, it's the operator for the surrounding + /// [SupportsOperation], and is used to determine whether parentheses are + /// necessary if [condition] is also a [SupportsOperation]. String _parenthesize(SupportsCondition condition, [String operator]) { if ((condition is SupportsNegation) || (condition is SupportsOperation && @@ -760,6 +794,8 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { return new SassString("$name(${arguments.map(valueToCss).join(', ')})"); } + /// Evaluates the arguments in [invocation] as applied to [callable], and + /// invokes [run] in a scope with those arguments defined. Value _runUserDefinedCallable(CallableInvocation invocation, UserDefinedCallable callable, Value run()) { var triple = _evaluateArguments(invocation); @@ -819,6 +855,8 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { }); } + /// Evaluates [invocation] as applied to [callable], and invokes [callable]'s + /// body. Value _runBuiltInCallable( CallableInvocation invocation, BuiltInCallable callable) { var triple = _evaluateArguments(invocation); @@ -881,6 +919,9 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { invocation.span); } + /// Evaluates the arguments in [invocation] and returns the positional and + /// named arguments, as well as the [ListSeparator] for the rest argument + /// list, if any. Tuple3, Map, ListSeparator> _evaluateArguments( CallableInvocation invocation) { var positional = invocation.arguments.positional @@ -925,6 +966,11 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { } } + /// Evaluates the arguments in [invocation] only as much as necessary to + /// separate out positional and named arguments. + /// + /// Returns the arguments as expressions so that they can be lazily evaluated + /// for macros such as `if()`. Tuple2, Map> _evaluateMacroArguments( CallableInvocation invocation) { if (invocation.arguments.rest == null) { @@ -966,6 +1012,13 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { } } + /// Adds the values in [map] to [values]. + /// + /// Throws a [SassRuntimeException] associated with [span] if any [map] keys + /// aren't strings. + /// + /// If [convert] is passed, that's used to convert the map values to the value + /// type for [values]. Otherwise, the [Value]s are used as-is. void _addRestMap/**/( Map values, SassMap map, FileSpan span, [/*=T*/ convert(Value value)]) { @@ -982,6 +1035,8 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { }); } + /// Throws a [SassRuntimeException] if [positional] and [named] aren't valid + /// when applied to [arguments]. void _verifyArguments(int positional, Map named, ArgumentDeclaration arguments, FileSpan span) { for (var i = 0; i < arguments.arguments.length; i++) { @@ -1031,6 +1086,7 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { // ## Utilities + /// Runs [callback] with [environment] as the current environment. /*=T*/ _withEnvironment/**/(Environment environment, /*=T*/ callback()) { var oldEnvironment = _environment; _environment = environment; @@ -1039,12 +1095,16 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { return result; } + /// Evaluates [interpolation] and wraps the result in a [CssValue]. + /// + /// If [trim] is `true`, removes whitespace around the result. CssValue _interpolationToValue(Interpolation interpolation, {bool trim: false}) { var result = _performInterpolation(interpolation); return new CssValue(trim ? result.trim() : result, interpolation.span); } + /// Evaluates [interpolation]. String _performInterpolation(Interpolation interpolation) { return interpolation.contents.map((value) { if (value is String) return value; @@ -1053,9 +1113,15 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { }).join(); } + /// Evaluates [expression] and wraps the result in a [CssValue]. CssValue _performExpression(Expression expression) => new CssValue(expression.accept(this), expression.span); + /// Adds [node] as a child of the current parent, then runs [callback] with + /// [node] as the current parent. + /// + /// If [through] is passed, [node] is added as a child of the first parent for + /// which [through] returns `false`. /*=T*/ _withParent/**/( /*=S*/ node, /*=T*/ callback(), @@ -1078,6 +1144,7 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { return result; } + /// Runs [callback] with [selector] as the current selector. /*=T*/ _withSelector/**/( CssValue selector, /*=T*/ callback()) { @@ -1088,6 +1155,7 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { return result; } + /// Runs [callback] with [queries] as the current media queries. /*=T*/ _withMediaQueries/**/( List queries, /*=T*/ callback()) { @@ -1098,6 +1166,10 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { return result; } + /// Adds a frame to the stack with the given [member] name, and [span] as the + /// site of the new frame. + /// + /// Runs [callback] with the new stack. /*=T*/ _withStackFrame/**/( String member, FileSpan span, /*=T*/ callback()) { _stack.add(_stackFrame(span)); @@ -1109,17 +1181,29 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { return result; } + /// Creates a new stack frame with location information from [span] and + /// [_member]. Frame _stackFrame(FileSpan span) => new Frame( span.sourceUrl, span.start.line + 1, span.start.column + 1, _member); + /// Returns a stack trace at the current point. + /// + /// [span] is the current location, used for the bottom-most stack frame. Trace _stackTrace(FileSpan span) { var frames = _stack.toList()..add(_stackFrame(span)); return new Trace(frames.reversed); } + /// Throws a [SassRuntimeException] with the given [message] and [span]. SassRuntimeException _exception(String message, FileSpan span) => new SassRuntimeException(message, span, _stackTrace(span)); + /// Runs [callback], and adjusts any [SassFormatException] to be within [span]. + /// + /// Specifically, this adjusts format exceptions so that the errors are + /// reported as though the text being parsed were exactly in [span]. This may + /// not be quite accurate if the source text contained interpolation, but + /// it'll still produce a useful error. /*=T*/ _adjustParseError/**/(FileSpan span, /*=T*/ callback()) { try { return callback(); @@ -1135,6 +1219,8 @@ class _PerformVisitor implements StatementVisitor, ExpressionVisitor { } } + /// Runs [callback], and converts any [InternalException]s it throws to + /// [SassRuntimeException]s with [span]. /*=T*/ _addExceptionSpan/**/(FileSpan span, /*=T*/ callback()) { try { return callback(); diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index 384fe8bd..c9de972f 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -16,6 +16,15 @@ import 'interface/css.dart'; import 'interface/selector.dart'; import 'interface/value.dart'; +/// Converts [node] to a CSS string. +/// +/// If [style] is passed, it controls the style of the resulting CSS. It +/// defaults to [OutputStyle.expanded]. +/// +/// If [inspect] is `true`, this will emit an unambiguous representation of the +/// source structure. Note however that, although this will be valid SCSS, it +/// may not be valid CSS. If [inspect] is `false` and [node] contains any values +/// that can't be represented in plain CSS, throws a [SassException]. String toCss(CssNode node, {OutputStyle style, bool inspect: false}) { var visitor = new _SerializeCssVisitor(style: style, inspect: inspect); node.accept(visitor); @@ -29,26 +38,41 @@ String toCss(CssNode node, {OutputStyle style, bool inspect: false}) { return result.trim(); } -// Note: this may throw an [InternalException] if [inspect] is `false`. +/// Converts [value] to a CSS string. +/// +/// If [inspect] is `true`, this will emit an unambiguous representation of the +/// source structure. Note however that, although this will be valid SCSS, it +/// may not be valid CSS. If [inspect] is `false` and [value] can't be +/// represented in plain CSS, throws an [InternalException]. String valueToCss(Value value, {bool inspect: false}) { var visitor = new _SerializeCssVisitor(inspect: inspect); value.accept(visitor); return visitor._buffer.toString(); } -// Note: this may throw an [InternalException] if [inspect] is `false`. +/// Converts [selector] to a CSS string. +/// +/// If [inspect] is `true`, this will emit an unambiguous representation of the +/// source structure. Note however that, although this will be valid SCSS, it +/// may not be valid CSS. If [inspect] is `false` and [selector] can't be +/// represented in plain CSS, throws an [InternalException]. String selectorToCss(Selector selector, {bool inspect: false}) { var visitor = new _SerializeCssVisitor(inspect: inspect); selector.accept(visitor); return visitor._buffer.toString(); } +/// A visitor that converts CSS syntax trees to plain strings. class _SerializeCssVisitor implements CssVisitor, ValueVisitor, SelectorVisitor { + /// A buffer that contains the CSS produced so far. final _buffer = new StringBuffer(); + /// The current indentation of the CSS output. var _indentation = 0; + /// Whether we're emitting an unambiguous representation of the source + /// structure, as opposed to valid CSS. final bool _inspect; _SerializeCssVisitor({OutputStyle style, bool inspect: false}) @@ -162,6 +186,9 @@ class _SerializeCssVisitor _buffer.writeCharCode($semicolon); } + /// Emits the value of [node] as a custom property value. + /// + /// This re-indents [node]'s value relative to the current indentation. void _writeCustomPropertyValue(CssDeclaration node) { var value = (node.value.value as SassString).text; @@ -179,6 +206,8 @@ class _SerializeCssVisitor _writeWithIndent(value, minimumIndentation); } + /// Returns the indentation level of the least-indented, non-empty line in + /// [text]. int _minimumIndentation(String text) { var scanner = new LineScanner(text); while (!scanner.isDone && scanner.readChar() != $lf) {} @@ -195,6 +224,8 @@ class _SerializeCssVisitor return min; } + /// Writes [text] to [_buffer], adding [minimumIndentation] to each non-empty + /// line. void _writeWithIndent(String text, int minimumIndentation) { var scanner = new LineScanner(text); while (!scanner.isDone && scanner.peekChar() != $lf) { @@ -213,6 +244,8 @@ class _SerializeCssVisitor // ## Values + /// Converts [value] to a plain CSS string, converting any + /// [InternalException]s to [SassException]s. void _visitValue(CssValue value) { try { value.value.accept(this); @@ -238,7 +271,9 @@ class _SerializeCssVisitor } } + /// Emits [color] as a hex character pair. void _writeHexComponent(int color) { + assert(color < 0x100); _buffer.writeCharCode(hexCharFor(color >> 4)); _buffer.writeCharCode(hexCharFor(color & 0xF)); } @@ -267,6 +302,8 @@ class _SerializeCssVisitor if (value.hasBrackets) _buffer.writeCharCode($rbracket); } + /// Returns whether [value] needs parentheses as an element in a list with the + /// given [separator]. bool _elementNeedsParens(ListSeparator separator, Value value) { if (value is SassList) { if (value.contents.length < 2) return false; @@ -289,6 +326,7 @@ class _SerializeCssVisitor _buffer.writeCharCode($rparen); } + /// Writes [value] as key or value in a map, with parentheses as necessary. void _writeMapElement(Value value) { var needsParens = value is SassList && value.separator == ListSeparator.comma && @@ -328,6 +366,11 @@ class _SerializeCssVisitor } } + /// Writes [string] to [_buffer]. + /// + /// By default, this detects which type of quote to use based on the contents + /// of the string. If [forceDoubleQuote] is `true`, this always uses a double + /// quote. void _visitString(String string, {bool forceDoubleQuote: false}) { var includesSingleQuote = false; var includesDoubleQuote = false; @@ -491,6 +534,7 @@ class _SerializeCssVisitor // ## Utilities + /// Emits [children] in a block. void _visitChildren(Iterable children) { _buffer.writeCharCode($lbrace); if (children.every(_isInvisible)) { @@ -510,6 +554,7 @@ class _SerializeCssVisitor _buffer.writeCharCode($rbrace); } + /// Writes indentation based on [_indentation]. void _writeIndentation() { for (var i = 0; i < _indentation; i++) { _buffer.writeCharCode($space); @@ -517,6 +562,8 @@ class _SerializeCssVisitor } } + /// Calls [callback] to write each value in [iterable], and writes [text] in + /// between each one. void _writeBetween/**/( Iterable/**/ iterable, String text, void callback(/*=T*/ value)) { var first = true; @@ -530,19 +577,23 @@ class _SerializeCssVisitor } } + /// Runs [callback] with indentation increased one level. void _indent(void callback()) { _indentation++; callback(); _indentation--; } + /// Returns whether [node] is considered invisible. bool _isInvisible(CssNode node) => !_inspect && node.isInvisible; } +/// An enum of generated CSS styles. class OutputStyle { + /// The standard CSS style, with each declaration on its own line. static const expanded = const OutputStyle._("expanded"); - static const nested = const OutputStyle._("nested"); + /// The name of the style. final String _name; const OutputStyle._(this._name);