mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-26 20:24:42 +01:00
Add shared interfaces for various AST nodes (#1445)
Fixes #1401 and #1414. Adds `Dependency`, `SassDeclaration`, and `SassReference` interfaces, which expose some getters that multiple AST nodes have in common with a single type. These also add getters for common subspans (URL, name, and namespace) to the interfaces.
This commit is contained in:
parent
fd7eec9eac
commit
d419df7d9c
@ -1,3 +1,7 @@
|
||||
## 1.38.1
|
||||
|
||||
* No user-visible changes
|
||||
|
||||
## 1.38.0
|
||||
|
||||
* In expanded mode, emit characters in Unicode private-use areas as escape
|
||||
|
@ -8,12 +8,15 @@ export 'sass/argument_invocation.dart';
|
||||
export 'sass/at_root_query.dart';
|
||||
export 'sass/callable_invocation.dart';
|
||||
export 'sass/configured_variable.dart';
|
||||
export 'sass/declaration.dart';
|
||||
export 'sass/dependency.dart';
|
||||
export 'sass/expression.dart';
|
||||
export 'sass/expression/binary_operation.dart';
|
||||
export 'sass/expression/boolean.dart';
|
||||
export 'sass/expression/color.dart';
|
||||
export 'sass/expression/function.dart';
|
||||
export 'sass/expression/if.dart';
|
||||
export 'sass/expression/interpolated_function.dart';
|
||||
export 'sass/expression/list.dart';
|
||||
export 'sass/expression/map.dart';
|
||||
export 'sass/expression/null.dart';
|
||||
@ -29,6 +32,7 @@ export 'sass/import/dynamic.dart';
|
||||
export 'sass/import/static.dart';
|
||||
export 'sass/interpolation.dart';
|
||||
export 'sass/node.dart';
|
||||
export 'sass/reference.dart';
|
||||
export 'sass/statement.dart';
|
||||
export 'sass/statement/at_root_rule.dart';
|
||||
export 'sass/statement/at_rule.dart';
|
||||
|
@ -6,14 +6,16 @@ import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../utils.dart';
|
||||
import '../../util/span.dart';
|
||||
import 'expression.dart';
|
||||
import 'declaration.dart';
|
||||
import 'node.dart';
|
||||
|
||||
/// An argument declared as part of an [ArgumentDeclaration].
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class Argument implements SassNode {
|
||||
class Argument implements SassNode, SassDeclaration {
|
||||
/// The argument name.
|
||||
final String name;
|
||||
|
||||
@ -30,6 +32,9 @@ class Argument implements SassNode {
|
||||
String get originalName =>
|
||||
defaultValue == null ? span.text : declarationName(span);
|
||||
|
||||
FileSpan get nameSpan =>
|
||||
defaultValue == null ? span : span.initialIdentifier(includeLeading: 1);
|
||||
|
||||
Argument(this.name, this.span, {this.defaultValue});
|
||||
|
||||
String toString() => defaultValue == null ? name : "$name: $defaultValue";
|
||||
|
@ -10,6 +10,7 @@ import '../../logger.dart';
|
||||
import '../../parse/scss.dart';
|
||||
import '../../utils.dart';
|
||||
import '../../util/character.dart';
|
||||
import '../../util/span.dart';
|
||||
import 'argument.dart';
|
||||
import 'node.dart';
|
||||
|
||||
|
@ -5,14 +5,16 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../util/span.dart';
|
||||
import 'expression.dart';
|
||||
import 'declaration.dart';
|
||||
import 'node.dart';
|
||||
|
||||
/// A variable configured by a `with` clause in a `@use` or `@forward` rule.
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class ConfiguredVariable implements SassNode {
|
||||
class ConfiguredVariable implements SassNode, SassDeclaration {
|
||||
/// The name of the variable being configured.
|
||||
final String name;
|
||||
|
||||
@ -26,6 +28,8 @@ class ConfiguredVariable implements SassNode {
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
FileSpan get nameSpan => span.initialIdentifier(includeLeading: 1);
|
||||
|
||||
ConfiguredVariable(this.name, this.expression, this.span,
|
||||
{bool guarded = false})
|
||||
: isGuarded = guarded;
|
||||
|
24
lib/src/ast/sass/declaration.dart
Normal file
24
lib/src/ast/sass/declaration.dart
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2021 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:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import 'node.dart';
|
||||
|
||||
/// A common interface for any node that declares a Sass member.
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
abstract class SassDeclaration extends SassNode {
|
||||
/// The name of the declaration, with underscores converted to hyphens.
|
||||
///
|
||||
/// This does not include the `$` for variables.
|
||||
String get name;
|
||||
|
||||
/// The span containing this declaration's name.
|
||||
///
|
||||
/// This includes the `$` for variables.
|
||||
FileSpan get nameSpan;
|
||||
}
|
20
lib/src/ast/sass/dependency.dart
Normal file
20
lib/src/ast/sass/dependency.dart
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2021 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:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import 'node.dart';
|
||||
|
||||
/// A common interface for [UseRule]s, [ForwardRule]s, and [DynamicImport]s.
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
abstract class SassDependency extends SassNode {
|
||||
/// The URL of the dependency this rule loads.
|
||||
Uri get url;
|
||||
|
||||
/// The span of the URL for this dependency, including the quotes.
|
||||
FileSpan get urlSpan;
|
||||
}
|
@ -5,38 +5,50 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../../util/span.dart';
|
||||
import '../../../visitor/interface/expression.dart';
|
||||
import '../expression.dart';
|
||||
import '../argument_invocation.dart';
|
||||
import '../callable_invocation.dart';
|
||||
import '../interpolation.dart';
|
||||
import '../reference.dart';
|
||||
|
||||
/// A function invocation.
|
||||
///
|
||||
/// This may be a plain CSS function or a Sass function.
|
||||
/// This may be a plain CSS function or a Sass function, but may not include
|
||||
/// interpolation.
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class FunctionExpression implements Expression, CallableInvocation {
|
||||
class FunctionExpression
|
||||
implements Expression, CallableInvocation, SassReference {
|
||||
/// The namespace of the function being invoked, or `null` if it's invoked
|
||||
/// without a namespace.
|
||||
final String? namespace;
|
||||
|
||||
/// The name of the function being invoked.
|
||||
///
|
||||
/// If [namespace] is non-`null`, underscores are converted to hyphens in this name.
|
||||
/// If [namespace] is `null`, this could be a plain CSS function call, so underscores are kept unchanged.
|
||||
///
|
||||
/// If this is interpolated, the function will be interpreted as plain CSS,
|
||||
/// even if it has the same name as a Sass function.
|
||||
final Interpolation name;
|
||||
/// The name of the function being invoked, with underscores left as-is.
|
||||
final String originalName;
|
||||
|
||||
/// The arguments to pass to the function.
|
||||
final ArgumentInvocation arguments;
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
FunctionExpression(this.name, this.arguments, this.span, {this.namespace});
|
||||
/// The name of the function being invoked, with underscores converted to
|
||||
/// hyphens.
|
||||
///
|
||||
/// If this function is a plain CSS function, use [originalName] instead.
|
||||
String get name => originalName.replaceAll('_', '-');
|
||||
|
||||
FileSpan get nameSpan {
|
||||
if (namespace == null) return span.initialIdentifier();
|
||||
return span.withoutNamespace().initialIdentifier();
|
||||
}
|
||||
|
||||
FileSpan? get namespaceSpan =>
|
||||
namespace == null ? null : span.initialIdentifier();
|
||||
|
||||
FunctionExpression(this.originalName, this.arguments, this.span,
|
||||
{this.namespace});
|
||||
|
||||
T accept<T>(ExpressionVisitor<T> visitor) =>
|
||||
visitor.visitFunctionExpression(this);
|
||||
@ -44,7 +56,7 @@ class FunctionExpression implements Expression, CallableInvocation {
|
||||
String toString() {
|
||||
var buffer = StringBuffer();
|
||||
if (namespace != null) buffer.write("$namespace.");
|
||||
buffer.write("$name$arguments");
|
||||
buffer.write("$originalName$arguments");
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
35
lib/src/ast/sass/expression/interpolated_function.dart
Normal file
35
lib/src/ast/sass/expression/interpolated_function.dart
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2021 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:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../../visitor/interface/expression.dart';
|
||||
import '../expression.dart';
|
||||
import '../argument_invocation.dart';
|
||||
import '../callable_invocation.dart';
|
||||
import '../interpolation.dart';
|
||||
|
||||
/// An interpolated function invocation.
|
||||
///
|
||||
/// This is always a plain CSS function.
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class InterpolatedFunctionExpression implements Expression, CallableInvocation {
|
||||
/// The name of the function being invoked.
|
||||
final Interpolation name;
|
||||
|
||||
/// The arguments to pass to the function.
|
||||
final ArgumentInvocation arguments;
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
InterpolatedFunctionExpression(this.name, this.arguments, this.span);
|
||||
|
||||
T accept<T>(ExpressionVisitor<T> visitor) =>
|
||||
visitor.visitInterpolatedFunctionExpression(this);
|
||||
|
||||
String toString() => '$name$arguments';
|
||||
}
|
@ -5,14 +5,16 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../../util/span.dart';
|
||||
import '../../../visitor/interface/expression.dart';
|
||||
import '../expression.dart';
|
||||
import '../reference.dart';
|
||||
|
||||
/// A Sass variable.
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class VariableExpression implements Expression {
|
||||
class VariableExpression implements Expression, SassReference {
|
||||
/// The namespace of the variable being referenced, or `null` if it's
|
||||
/// referenced without a namespace.
|
||||
final String? namespace;
|
||||
@ -22,6 +24,14 @@ class VariableExpression implements Expression {
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
FileSpan get nameSpan {
|
||||
if (namespace == null) return span;
|
||||
return span.withoutNamespace();
|
||||
}
|
||||
|
||||
FileSpan? get namespaceSpan =>
|
||||
namespace == null ? null : span.initialIdentifier();
|
||||
|
||||
VariableExpression(this.name, this.span, {this.namespace});
|
||||
|
||||
T accept<T>(ExpressionVisitor<T> visitor) =>
|
||||
|
@ -6,13 +6,14 @@ import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../expression/string.dart';
|
||||
import '../dependency.dart';
|
||||
import '../import.dart';
|
||||
|
||||
/// An import that will load a Sass file at runtime.
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class DynamicImport implements Import {
|
||||
class DynamicImport implements Import, SassDependency {
|
||||
/// The URL of the file to import.
|
||||
///
|
||||
/// If this is relative, it's relative to the containing file.
|
||||
@ -30,6 +31,7 @@ class DynamicImport implements Import {
|
||||
final String urlString;
|
||||
|
||||
final FileSpan span;
|
||||
FileSpan get urlSpan => span;
|
||||
|
||||
DynamicImport(this.urlString, this.span);
|
||||
|
||||
|
33
lib/src/ast/sass/reference.dart
Normal file
33
lib/src/ast/sass/reference.dart
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2021 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:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import 'node.dart';
|
||||
|
||||
/// A common interface for any node that references a Sass member.
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
abstract class SassReference extends SassNode {
|
||||
/// The namespace of the member being referenced, or `null` if it's referenced
|
||||
/// without a namespace.
|
||||
String? get namespace;
|
||||
|
||||
/// The name of the member being referenced, with underscores converted to
|
||||
/// hyphens.
|
||||
///
|
||||
/// This does not include the `$` for variables.
|
||||
String get name;
|
||||
|
||||
/// The span containing this reference's name.
|
||||
///
|
||||
/// For variables, this should include the `$`.
|
||||
FileSpan get nameSpan;
|
||||
|
||||
/// The span containing this reference's namespace, null if [namespace] is
|
||||
/// null.
|
||||
FileSpan? get namespaceSpan;
|
||||
}
|
@ -6,8 +6,10 @@ import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../../util/span.dart';
|
||||
import '../../../visitor/interface/statement.dart';
|
||||
import '../configured_variable.dart';
|
||||
import '../dependency.dart';
|
||||
import '../expression/string.dart';
|
||||
import '../statement.dart';
|
||||
|
||||
@ -15,7 +17,7 @@ import '../statement.dart';
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class ForwardRule implements Statement {
|
||||
class ForwardRule implements Statement, SassDependency {
|
||||
/// The URI of the module to forward.
|
||||
///
|
||||
/// If this is relative, it's relative to the containing file.
|
||||
@ -74,6 +76,8 @@ class ForwardRule implements Statement {
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
FileSpan get urlSpan => span.withoutInitialAtRule().initialQuoted();
|
||||
|
||||
/// Creates a `@forward` rule that allows all members to be accessed.
|
||||
ForwardRule(this.url, this.span,
|
||||
{this.prefix, Iterable<ConfiguredVariable>? configuration})
|
||||
|
@ -5,8 +5,10 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../../util/span.dart';
|
||||
import '../../../visitor/interface/statement.dart';
|
||||
import '../argument_declaration.dart';
|
||||
import '../declaration.dart';
|
||||
import '../statement.dart';
|
||||
import 'callable_declaration.dart';
|
||||
import 'silent_comment.dart';
|
||||
@ -17,7 +19,9 @@ import 'silent_comment.dart';
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class FunctionRule extends CallableDeclaration {
|
||||
class FunctionRule extends CallableDeclaration implements SassDeclaration {
|
||||
FileSpan get nameSpan => span.withoutInitialAtRule().initialIdentifier();
|
||||
|
||||
FunctionRule(String name, ArgumentDeclaration arguments,
|
||||
Iterable<Statement> children, FileSpan span,
|
||||
{SilentComment? comment})
|
||||
|
@ -5,10 +5,11 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../../utils.dart';
|
||||
import '../../../util/span.dart';
|
||||
import '../../../visitor/interface/statement.dart';
|
||||
import '../argument_invocation.dart';
|
||||
import '../callable_invocation.dart';
|
||||
import '../reference.dart';
|
||||
import '../statement.dart';
|
||||
import 'content_block.dart';
|
||||
|
||||
@ -16,7 +17,7 @@ import 'content_block.dart';
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class IncludeRule implements Statement, CallableInvocation {
|
||||
class IncludeRule implements Statement, CallableInvocation, SassReference {
|
||||
/// The namespace of the mixin being invoked, or `null` if it's invoked
|
||||
/// without a namespace.
|
||||
final String? namespace;
|
||||
@ -39,6 +40,22 @@ class IncludeRule implements Statement, CallableInvocation {
|
||||
? span
|
||||
: span.file.span(span.start.offset, arguments.span.end.offset).trim();
|
||||
|
||||
FileSpan get nameSpan {
|
||||
var startSpan = span.text.startsWith('+')
|
||||
? span.subspan(1).trimLeft()
|
||||
: span.withoutInitialAtRule();
|
||||
if (namespace != null) startSpan = startSpan.withoutNamespace();
|
||||
return startSpan.initialIdentifier();
|
||||
}
|
||||
|
||||
FileSpan? get namespaceSpan {
|
||||
if (namespace == null) return null;
|
||||
var startSpan = span.text.startsWith('+')
|
||||
? span.subspan(1).trimLeft()
|
||||
: span.withoutInitialAtRule();
|
||||
return startSpan.initialIdentifier();
|
||||
}
|
||||
|
||||
IncludeRule(this.name, this.arguments, this.span,
|
||||
{this.namespace, this.content});
|
||||
|
||||
|
@ -5,9 +5,11 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
import '../../../util/span.dart';
|
||||
import '../../../visitor/interface/statement.dart';
|
||||
import '../../../visitor/statement_search.dart';
|
||||
import '../argument_declaration.dart';
|
||||
import '../declaration.dart';
|
||||
import '../statement.dart';
|
||||
import 'callable_declaration.dart';
|
||||
import 'content_rule.dart';
|
||||
@ -19,11 +21,18 @@ import 'silent_comment.dart';
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class MixinRule extends CallableDeclaration {
|
||||
class MixinRule extends CallableDeclaration implements SassDeclaration {
|
||||
/// Whether the mixin contains a `@content` rule.
|
||||
late final bool hasContent =
|
||||
const _HasContentVisitor().visitMixinRule(this) == true;
|
||||
|
||||
FileSpan get nameSpan {
|
||||
var startSpan = span.text.startsWith('=')
|
||||
? span.subspan(1).trimLeft()
|
||||
: span.withoutInitialAtRule();
|
||||
return startSpan.initialIdentifier();
|
||||
}
|
||||
|
||||
MixinRule(String name, ArgumentDeclaration arguments,
|
||||
Iterable<Statement> children, FileSpan span,
|
||||
{SilentComment? comment})
|
||||
|
@ -8,8 +8,10 @@ import 'package:source_span/source_span.dart';
|
||||
import '../../../exception.dart';
|
||||
import '../../../logger.dart';
|
||||
import '../../../parse/scss.dart';
|
||||
import '../../../util/span.dart';
|
||||
import '../../../visitor/interface/statement.dart';
|
||||
import '../configured_variable.dart';
|
||||
import '../dependency.dart';
|
||||
import '../expression/string.dart';
|
||||
import '../statement.dart';
|
||||
|
||||
@ -17,7 +19,7 @@ import '../statement.dart';
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class UseRule implements Statement {
|
||||
class UseRule implements Statement, SassDependency {
|
||||
/// The URI of the module to use.
|
||||
///
|
||||
/// If this is relative, it's relative to the containing file.
|
||||
@ -32,6 +34,8 @@ class UseRule implements Statement {
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
FileSpan get urlSpan => span.withoutInitialAtRule().initialQuoted();
|
||||
|
||||
UseRule(this.url, this.namespace, this.span,
|
||||
{Iterable<ConfiguredVariable>? configuration})
|
||||
: configuration = configuration == null
|
||||
|
@ -9,8 +9,10 @@ import '../../../exception.dart';
|
||||
import '../../../logger.dart';
|
||||
import '../../../parse/scss.dart';
|
||||
import '../../../utils.dart';
|
||||
import '../../../util/span.dart';
|
||||
import '../../../visitor/interface/statement.dart';
|
||||
import '../expression.dart';
|
||||
import '../declaration.dart';
|
||||
import '../statement.dart';
|
||||
import 'silent_comment.dart';
|
||||
|
||||
@ -20,12 +22,12 @@ import 'silent_comment.dart';
|
||||
///
|
||||
/// {@category AST}
|
||||
@sealed
|
||||
class VariableDeclaration implements Statement {
|
||||
class VariableDeclaration implements Statement, SassDeclaration {
|
||||
/// The namespace of the variable being set, or `null` if it's defined or set
|
||||
/// without a namespace.
|
||||
final String? namespace;
|
||||
|
||||
/// The name of the variable.
|
||||
/// The name of the variable, with underscores converted to hyphens.
|
||||
final String name;
|
||||
|
||||
/// The comment immediately preceding this declaration.
|
||||
@ -53,6 +55,15 @@ class VariableDeclaration implements Statement {
|
||||
/// messages.
|
||||
String get originalName => declarationName(span);
|
||||
|
||||
FileSpan get nameSpan {
|
||||
var span = this.span;
|
||||
if (namespace != null) span = span.withoutNamespace();
|
||||
return span.initialIdentifier(includeLeading: 1);
|
||||
}
|
||||
|
||||
FileSpan? get namespaceSpan =>
|
||||
namespace == null ? null : span.initialIdentifier();
|
||||
|
||||
VariableDeclaration(this.name, this.expression, this.span,
|
||||
{this.namespace,
|
||||
bool guarded = false,
|
||||
|
@ -128,7 +128,7 @@ class CssParser extends ScssParser {
|
||||
"This function isn't allowed in plain CSS.", scanner.spanFrom(start));
|
||||
}
|
||||
|
||||
return FunctionExpression(
|
||||
return InterpolatedFunctionExpression(
|
||||
// Create a fake interpolation to force the function to be interpreted
|
||||
// as plain CSS, rather than calling a user-defined function.
|
||||
Interpolation([StringExpression(identifier)], identifier.span),
|
||||
|
@ -479,35 +479,7 @@ class Parser {
|
||||
|
||||
/// Consumes an escape sequence and returns the character it represents.
|
||||
@protected
|
||||
int escapeCharacter() {
|
||||
// See https://drafts.csswg.org/css-syntax-3/#consume-escaped-code-point.
|
||||
|
||||
scanner.expectChar($backslash);
|
||||
var first = scanner.peekChar();
|
||||
if (first == null) {
|
||||
return 0xFFFD;
|
||||
} else if (isNewline(first)) {
|
||||
scanner.error("Expected escape sequence.");
|
||||
} else if (isHex(first)) {
|
||||
var value = 0;
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var next = scanner.peekChar();
|
||||
if (next == null || !isHex(next)) break;
|
||||
value = (value << 4) + asHex(scanner.readChar());
|
||||
}
|
||||
if (isWhitespace(scanner.peekChar())) scanner.readChar();
|
||||
|
||||
if (value == 0 ||
|
||||
(value >= 0xD800 && value <= 0xDFFF) ||
|
||||
value >= 0x10FFFF) {
|
||||
return 0xFFFD;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return scanner.readChar();
|
||||
}
|
||||
}
|
||||
int escapeCharacter() => consumeEscapedCharacter(scanner);
|
||||
|
||||
// Consumes the next character if it matches [condition].
|
||||
//
|
||||
|
@ -2661,17 +2661,18 @@ abstract class StylesheetParser extends Parser {
|
||||
namespace: plain);
|
||||
}
|
||||
|
||||
var beforeName = scanner.state;
|
||||
var name =
|
||||
Interpolation([_publicIdentifier()], scanner.spanFrom(beforeName));
|
||||
|
||||
return FunctionExpression(
|
||||
name, _argumentInvocation(), scanner.spanFrom(start),
|
||||
_publicIdentifier(), _argumentInvocation(), scanner.spanFrom(start),
|
||||
namespace: plain);
|
||||
|
||||
case $lparen:
|
||||
return FunctionExpression(
|
||||
if (plain == null) {
|
||||
return InterpolatedFunctionExpression(
|
||||
identifier, _argumentInvocation(), scanner.spanFrom(start));
|
||||
} else {
|
||||
return FunctionExpression(
|
||||
plain, _argumentInvocation(), scanner.spanFrom(start));
|
||||
}
|
||||
|
||||
default:
|
||||
return StringExpression(identifier);
|
||||
@ -2949,8 +2950,10 @@ abstract class StylesheetParser extends Parser {
|
||||
var contents = _tryUrlContents(start);
|
||||
if (contents != null) return StringExpression(contents);
|
||||
|
||||
return FunctionExpression(Interpolation(["url"], scanner.spanFrom(start)),
|
||||
_argumentInvocation(), scanner.spanFrom(start));
|
||||
return InterpolatedFunctionExpression(
|
||||
Interpolation(["url"], scanner.spanFrom(start)),
|
||||
_argumentInvocation(),
|
||||
scanner.spanFrom(start));
|
||||
}
|
||||
|
||||
/// Consumes tokens up to "{", "}", ";", or "!".
|
||||
|
94
lib/src/util/span.dart
Normal file
94
lib/src/util/span.dart
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright 2021 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:charcode/charcode.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
import '../utils.dart';
|
||||
import 'character.dart';
|
||||
|
||||
extension SpanExtensions on FileSpan {
|
||||
/// Returns this span with all whitespace trimmed from both sides.
|
||||
FileSpan trim() => trimLeft().trimRight();
|
||||
|
||||
/// Returns this span with all leading whitespace trimmed.
|
||||
FileSpan trimLeft() {
|
||||
var start = 0;
|
||||
while (isWhitespace(text.codeUnitAt(start))) {
|
||||
start++;
|
||||
}
|
||||
return subspan(start);
|
||||
}
|
||||
|
||||
/// Returns this span with all trailing whitespace trimmed.
|
||||
FileSpan trimRight() {
|
||||
var end = text.length - 1;
|
||||
while (isWhitespace(text.codeUnitAt(end))) {
|
||||
end--;
|
||||
}
|
||||
return subspan(0, end + 1);
|
||||
}
|
||||
|
||||
/// Returns the span of the identifier at the start of this span.
|
||||
///
|
||||
/// If [includeLeading] is greater than 0, that many additional characters
|
||||
/// will be included from the start of this span before looking for an
|
||||
/// identifier.
|
||||
FileSpan initialIdentifier({int includeLeading = 0}) {
|
||||
var scanner = StringScanner(text);
|
||||
for (var i = 0; i < includeLeading; i++) {
|
||||
scanner.readChar();
|
||||
}
|
||||
_scanIdentifier(scanner);
|
||||
return subspan(0, scanner.position);
|
||||
}
|
||||
|
||||
/// Returns a subspan excluding the identifier at the start of this span.
|
||||
FileSpan withoutInitialIdentifier() {
|
||||
var scanner = StringScanner(text);
|
||||
_scanIdentifier(scanner);
|
||||
return subspan(scanner.position);
|
||||
}
|
||||
|
||||
/// Returns a subspan excluding a namespace and `.` at the start of this span.
|
||||
FileSpan withoutNamespace() => withoutInitialIdentifier().subspan(1);
|
||||
|
||||
/// Returns the span of the quoted text at the start of this span.
|
||||
///
|
||||
/// This span must start with `"` or `'`.
|
||||
FileSpan initialQuoted() {
|
||||
var scanner = StringScanner(text);
|
||||
var quote = scanner.readChar();
|
||||
while (true) {
|
||||
var next = scanner.readChar();
|
||||
if (next == quote) break;
|
||||
if (next == $backslash) scanner.readChar();
|
||||
}
|
||||
return subspan(0, scanner.position);
|
||||
}
|
||||
|
||||
/// Returns a subspan excluding an initial at-rule and any whitespace after
|
||||
/// it.
|
||||
FileSpan withoutInitialAtRule() {
|
||||
var scanner = StringScanner(text);
|
||||
scanner.expectChar($at);
|
||||
_scanIdentifier(scanner);
|
||||
return subspan(scanner.position).trimLeft();
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes an identifier from [scanner].
|
||||
void _scanIdentifier(StringScanner scanner) {
|
||||
while (!scanner.isDone) {
|
||||
var char = scanner.peekChar()!;
|
||||
if (char == $backslash) {
|
||||
consumeEscapedCharacter(scanner);
|
||||
} else if (isName(char)) {
|
||||
scanner.readChar();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import 'package:charcode/charcode.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'package:stack_trace/stack_trace.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
import 'package:term_glyph/term_glyph.dart' as glyph;
|
||||
|
||||
import 'util/character.dart';
|
||||
@ -383,24 +384,35 @@ Map<K1, Map<K2, V>> copyMapOfMap<K1, K2, V>(Map<K1, Map<K2, V>> map) =>
|
||||
Map<K, List<E>> copyMapOfList<K, E>(Map<K, List<E>> map) =>
|
||||
{for (var entry in map.entries) entry.key: entry.value.toList()};
|
||||
|
||||
extension SpanExtensions on FileSpan {
|
||||
/// Returns this span with all whitespace trimmed from both sides.
|
||||
FileSpan trim() {
|
||||
var text = this.text;
|
||||
/// Consumes an escape sequence from [scanner] and returns the character it
|
||||
/// represents.
|
||||
int consumeEscapedCharacter(StringScanner scanner) {
|
||||
// See https://drafts.csswg.org/css-syntax-3/#consume-escaped-code-point.
|
||||
|
||||
var start = 0;
|
||||
while (isWhitespace(text.codeUnitAt(start))) {
|
||||
start++;
|
||||
scanner.expectChar($backslash);
|
||||
var first = scanner.peekChar();
|
||||
if (first == null) {
|
||||
return 0xFFFD;
|
||||
} else if (isNewline(first)) {
|
||||
scanner.error("Expected escape sequence.");
|
||||
} else if (isHex(first)) {
|
||||
var value = 0;
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var next = scanner.peekChar();
|
||||
if (next == null || !isHex(next)) break;
|
||||
value = (value << 4) + asHex(scanner.readChar());
|
||||
}
|
||||
if (isWhitespace(scanner.peekChar())) scanner.readChar();
|
||||
|
||||
var end = text.length - 1;
|
||||
while (isWhitespace(text.codeUnitAt(end))) {
|
||||
end--;
|
||||
if (value == 0 ||
|
||||
(value >= 0xD800 && value <= 0xDFFF) ||
|
||||
value >= 0x10FFFF) {
|
||||
return 0xFFFD;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
||||
return start == 0 && end == text.length - 1
|
||||
? this
|
||||
: file.span(this.start.offset + start, this.start.offset + end + 1);
|
||||
} else {
|
||||
return scanner.readChar();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,10 +433,8 @@ class _EvaluateVisitor
|
||||
deprecation: true);
|
||||
|
||||
var callableNode = _callableNode!;
|
||||
var expression = FunctionExpression(
|
||||
Interpolation([function.text], callableNode.span),
|
||||
invocation,
|
||||
callableNode.span);
|
||||
var expression =
|
||||
FunctionExpression(function.text, invocation, callableNode.span);
|
||||
return await expression.accept(this);
|
||||
}
|
||||
|
||||
@ -2227,26 +2225,15 @@ class _EvaluateVisitor
|
||||
}
|
||||
|
||||
Future<Value> visitFunctionExpression(FunctionExpression node) async {
|
||||
var plainName = node.name.asPlain;
|
||||
AsyncCallable? function;
|
||||
if (plainName != null) {
|
||||
function = _addExceptionSpan(
|
||||
node,
|
||||
() => _getFunction(
|
||||
// If the node has a namespace, the plain name was already
|
||||
// normalized at parse-time so we don't need to renormalize here.
|
||||
node.namespace == null
|
||||
? plainName.replaceAll("_", "-")
|
||||
: plainName,
|
||||
namespace: node.namespace));
|
||||
}
|
||||
var function = _addExceptionSpan(
|
||||
node, () => _getFunction(node.name, namespace: node.namespace));
|
||||
|
||||
if (function == null) {
|
||||
if (node.namespace != null) {
|
||||
throw _exception("Undefined function.", node.span);
|
||||
}
|
||||
|
||||
function = PlainCssCallable(await _performInterpolation(node.name));
|
||||
function = PlainCssCallable(node.originalName);
|
||||
}
|
||||
|
||||
var oldInFunction = _inFunction;
|
||||
@ -2257,6 +2244,17 @@ class _EvaluateVisitor
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<Value> visitInterpolatedFunctionExpression(
|
||||
InterpolatedFunctionExpression node) async {
|
||||
var function = PlainCssCallable(await _performInterpolation(node.name));
|
||||
var oldInFunction = _inFunction;
|
||||
_inFunction = true;
|
||||
var result = await _addErrorSpan(
|
||||
node, () => _runFunctionCallable(node.arguments, function, node));
|
||||
_inFunction = oldInFunction;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Like `_environment.getFunction`, but also returns built-in
|
||||
/// globally-available functions.
|
||||
AsyncCallable? _getFunction(String name, {String? namespace}) {
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_evaluate.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: ffd96390e470c2feb704a96bc4092c62a8209989
|
||||
// Checksum: 37fd148527c89ecfa79b51500b589b27a83f542e
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
@ -440,10 +440,8 @@ class _EvaluateVisitor
|
||||
deprecation: true);
|
||||
|
||||
var callableNode = _callableNode!;
|
||||
var expression = FunctionExpression(
|
||||
Interpolation([function.text], callableNode.span),
|
||||
invocation,
|
||||
callableNode.span);
|
||||
var expression =
|
||||
FunctionExpression(function.text, invocation, callableNode.span);
|
||||
return expression.accept(this);
|
||||
}
|
||||
|
||||
@ -2215,26 +2213,15 @@ class _EvaluateVisitor
|
||||
}
|
||||
|
||||
Value visitFunctionExpression(FunctionExpression node) {
|
||||
var plainName = node.name.asPlain;
|
||||
Callable? function;
|
||||
if (plainName != null) {
|
||||
function = _addExceptionSpan(
|
||||
node,
|
||||
() => _getFunction(
|
||||
// If the node has a namespace, the plain name was already
|
||||
// normalized at parse-time so we don't need to renormalize here.
|
||||
node.namespace == null
|
||||
? plainName.replaceAll("_", "-")
|
||||
: plainName,
|
||||
namespace: node.namespace));
|
||||
}
|
||||
var function = _addExceptionSpan(
|
||||
node, () => _getFunction(node.name, namespace: node.namespace));
|
||||
|
||||
if (function == null) {
|
||||
if (node.namespace != null) {
|
||||
throw _exception("Undefined function.", node.span);
|
||||
}
|
||||
|
||||
function = PlainCssCallable(_performInterpolation(node.name));
|
||||
function = PlainCssCallable(node.originalName);
|
||||
}
|
||||
|
||||
var oldInFunction = _inFunction;
|
||||
@ -2245,6 +2232,17 @@ class _EvaluateVisitor
|
||||
return result;
|
||||
}
|
||||
|
||||
Value visitInterpolatedFunctionExpression(
|
||||
InterpolatedFunctionExpression node) {
|
||||
var function = PlainCssCallable(_performInterpolation(node.name));
|
||||
var oldInFunction = _inFunction;
|
||||
_inFunction = true;
|
||||
var result = _addErrorSpan(
|
||||
node, () => _runFunctionCallable(node.arguments, function, node));
|
||||
_inFunction = oldInFunction;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Like `_environment.getFunction`, but also returns built-in
|
||||
/// globally-available functions.
|
||||
Callable? _getFunction(String name, {String? namespace}) {
|
||||
|
@ -13,6 +13,7 @@ abstract class ExpressionVisitor<T> {
|
||||
T visitBinaryOperationExpression(BinaryOperationExpression node);
|
||||
T visitBooleanExpression(BooleanExpression node);
|
||||
T visitColorExpression(ColorExpression node);
|
||||
T visitInterpolatedFunctionExpression(InterpolatedFunctionExpression node);
|
||||
T visitFunctionExpression(FunctionExpression node);
|
||||
T visitIfExpression(IfExpression node);
|
||||
T visitListExpression(ListExpression node);
|
||||
|
@ -161,6 +161,11 @@ abstract class RecursiveAstVisitor extends RecursiveStatementVisitor
|
||||
}
|
||||
|
||||
void visitFunctionExpression(FunctionExpression node) {
|
||||
visitArgumentInvocation(node.arguments);
|
||||
}
|
||||
|
||||
void visitInterpolatedFunctionExpression(
|
||||
InterpolatedFunctionExpression node) {
|
||||
visitInterpolation(node.name);
|
||||
visitArgumentInvocation(node.arguments);
|
||||
}
|
||||
|
@ -1,3 +1,23 @@
|
||||
## 1.0.0-beta.4
|
||||
|
||||
* `UseRule`, `ForwardRule`, and `DynamicImport` now share a common `Dependency`
|
||||
interface that exposes a `url` getter and a `urlSpan` getter.
|
||||
|
||||
* `VariableDeclaration`, `MixinRule`, `FunctionRule`, `Argument`, and
|
||||
`ConfiguredVariable` now share a common `SassDeclaration` interface that
|
||||
exposes a `name` getter (with underscores converted to hyphens) and a
|
||||
`nameSpan` getter.
|
||||
|
||||
* Function calls with interpolation have now been split into their own AST node:
|
||||
`InterpolatedFunctionExpression`. `FunctionExpression.name` is now always a
|
||||
string (with underscores converted to hyphens). `FunctionExpression` also now
|
||||
has an `originalName` getter, which leaves underscores as-is.
|
||||
|
||||
* `VariableExpression`, `IncludeRule`, and `FunctionExpression` now share a
|
||||
common `SassReference` interface that exposes a `namespace` getter and a
|
||||
`name` getter (with underscores converted to hyphens), as well as
|
||||
corresponding `namespaceSpan` and `nameSpan` getters.
|
||||
|
||||
## 1.0.0-beta.3
|
||||
|
||||
* No user-visible changes.
|
||||
|
@ -2,7 +2,7 @@ name: sass_api
|
||||
# Note: Every time we add a new Sass AST node, we need to bump the *major*
|
||||
# version because it's a breaking change for anyone who's implementing the
|
||||
# visitor interface(s).
|
||||
version: 1.0.0-beta.3
|
||||
version: 1.0.0-beta.4
|
||||
description: Additional APIs for Dart Sass.
|
||||
homepage: https://github.com/sass/dart-sass
|
||||
|
||||
@ -10,7 +10,7 @@ environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
sass: 1.38.0
|
||||
sass: 1.38.1
|
||||
|
||||
dependency_overrides:
|
||||
sass: {path: ../..}
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: sass
|
||||
version: 1.38.0
|
||||
version: 1.38.1
|
||||
description: A Sass implementation in Dart.
|
||||
homepage: https://github.com/sass/dart-sass
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user