Skip to content

Commit

Permalink
Add support for the JS API value types (#1552)
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Nov 30, 2021
1 parent 926781b commit 97c51d6
Show file tree
Hide file tree
Showing 46 changed files with 1,064 additions and 401 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 1.44.0

### Dart API

* Add `SassNumber.convert()` and `SassNumber.convertValue()`. These work like
`SassNumber.coerce()` and `SassNumber.coerceValue()`, except they don't treat
unitless numbers as universally compatible.

* Fix a bug where `SassNumber.coerceToMatch()` and
`SassNumber.coerceValueToMatch()` wouldn't coerce single-unit numbers to
match unitless numbers.

## 1.43.5

* Fix a bug where calculations with different operators were incorrectly
Expand Down
50 changes: 32 additions & 18 deletions lib/src/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:js/js.dart';

import 'node/exception.dart';
import 'node/exports.dart';
import 'node/compile.dart';
Expand All @@ -13,21 +11,37 @@ import 'node/legacy/value.dart';
import 'node/logger.dart';
import 'node/source_span.dart';
import 'node/utils.dart';
import 'node/value.dart';
import 'value.dart';

/// The entrypoint for the Node.js module.
///
/// This sets up exports that can be called from JS.
void main() {
if (const bool.fromEnvironment("new-js-api")) {
exports.compile = allowInterop(compile);
exports.compileString = allowInterop(compileString);
exports.compileAsync = allowInterop(compileAsync);
exports.compileStringAsync = allowInterop(compileStringAsync);
exports.Exception = exceptionConstructor;
exports.compile = allowInteropNamed('sass.compile', compile);
exports.compileString =
allowInteropNamed('sass.compileString', compileString);
exports.compileAsync = allowInteropNamed('sass.compileAsync', compileAsync);
exports.compileStringAsync =
allowInteropNamed('sass.compileStringAsync', compileStringAsync);
exports.Value = valueClass;
exports.SassBoolean = booleanClass;
exports.SassArgumentList = argumentListClass;
exports.SassColor = colorClass;
exports.SassFunction = functionClass;
exports.SassList = listClass;
exports.SassMap = mapClass;
exports.SassNumber = numberClass;
exports.SassString = stringClass;
exports.sassNull = sassNull;
exports.sassTrue = sassTrue;
exports.sassFalse = sassFalse;
exports.Exception = exceptionClass;
exports.Logger = LoggerNamespace(
silent: NodeLogger(
warn: allowInterop((_, __) {}), debug: allowInterop((_, __) {})));
warn: allowInteropNamed('sass.Logger.silent.warn', (_, __) {}),
debug: allowInteropNamed('sass.Logger.silent.debug', (_, __) {})));
}

exports.info =
Expand All @@ -39,18 +53,18 @@ void main() {
updateSourceSpanPrototype();

// Legacy API
exports.render = allowInterop(render);
exports.renderSync = allowInterop(renderSync);
exports.render = allowInteropNamed('sass.render', render);
exports.renderSync = allowInteropNamed('sass.renderSync', renderSync);

exports.types = Types(
Boolean: booleanConstructor,
Color: colorConstructor,
List: listConstructor,
Map: mapConstructor,
Null: nullConstructor,
Number: numberConstructor,
String: stringConstructor,
Error: jsErrorConstructor);
Boolean: legacyBooleanClass,
Color: legacyColorClass,
List: legacyListClass,
Map: legacyMapClass,
Null: legacyNullClass,
Number: legacyNumberClass,
String: legacyStringClass,
Error: jsErrorClass);
exports.NULL = sassNull;
exports.TRUE = sassTrue;
exports.FALSE = sassFalse;
Expand Down
18 changes: 9 additions & 9 deletions lib/src/node/exception.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'dart:js_util';

import 'package:js/js.dart';
import 'package:node_interop/node_interop.dart';
import 'package:term_glyph/term_glyph.dart' as glyph;

import '../exception.dart';
import '../utils.dart';
import 'reflection.dart';
import 'utils.dart';

@JS()
Expand All @@ -22,12 +21,12 @@ class _NodeException extends JsError {
external SassException get _dartException;
}

/// The constructor for Sass's JS API exception class.
var exceptionConstructor = () {
/// Sass's JS API exception class.
final JSClass exceptionClass = () {
// There's no way to define this in pure Dart, because the only way to create
// a subclass of the JS `Error` type that sets its internal `[[ErrorData]]`
// field is to call `super()` with the ES6 class syntax.
var klass = jsEval(r'''
var jsClass = jsEval(r'''
return class Exception extends Error {
constructor(dartException, message) {
super(message);
Expand All @@ -44,17 +43,18 @@ var exceptionConstructor = () {
return this.message;
}
}
''') as Function;
''') as JSClass;
jsClass.setName('sass.Exception');

addGetters(klass, {
jsClass.defineGetters({
'sassMessage': (_NodeException exception) =>
exception._dartException.message,
'sassStack': (_NodeException exception) =>
exception._dartException.trace.toString(),
'span': (_NodeException exception) => exception._dartException.span
});

return klass;
return jsClass;
}();

/// Wraps [exception] with a Node API exception and throws it.
Expand All @@ -71,7 +71,7 @@ Never throwNodeException(SassException exception,
var wasAscii = glyph.ascii;
glyph.ascii = ascii;
try {
var jsException = callConstructor(exceptionConstructor, [
var jsException = exceptionClass.construct([
exception,
exception.toString(color: color).replaceFirst('Error: ', '')
]) as _NodeException;
Expand Down
25 changes: 20 additions & 5 deletions lib/src/node/exports.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

import 'package:js/js.dart';

import '../value.dart';
import '../value.dart' as value;
import 'legacy/types.dart';
import 'logger.dart';
import 'reflection.dart';

@JS()
class Exports {
Expand All @@ -19,16 +20,30 @@ class Exports {
external set compile(Function function);
external set compileAsync(Function function);
external set info(String info);
external set Exception(Function function);
external set Exception(JSClass function);
external set Logger(LoggerNamespace namespace);

// Value APIs
external set Value(JSClass function);
external set SassArgumentList(JSClass function);
external set SassBoolean(JSClass function);
external set SassColor(JSClass function);
external set SassFunction(JSClass function);
external set SassList(JSClass function);
external set SassMap(JSClass function);
external set SassNumber(JSClass function);
external set SassString(JSClass function);
external set sassNull(value.Value sassNull);
external set sassTrue(value.SassBoolean sassTrue);
external set sassFalse(value.SassBoolean sassFalse);

// Legacy APIs
external set run_(Function function);
external set render(Function function);
external set types(Types types);
external set NULL(Value sassNull);
external set TRUE(SassBoolean sassTrue);
external set FALSE(SassBoolean sassFalse);
external set NULL(value.Value sassNull);
external set TRUE(value.SassBoolean sassTrue);
external set FALSE(value.SassBoolean sassFalse);
}

@JS()
Expand Down
52 changes: 52 additions & 0 deletions lib/src/node/immutable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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:js/js.dart';

@JS('immutable.List')
class ImmutableList {
external factory ImmutableList([List<Object?>? contents]);

external List<Object?> toArray();
}

@JS('immutable.OrderedMap')
class ImmutableMap {
external factory ImmutableMap([List<List<Object?>>? entries]);

external ImmutableMap asMutable();
external ImmutableMap asImmutable();
external ImmutableMap set(Object key, Object? value);
external Object toObject();
external void forEach(void Function(Object?, Object, Object?) callback);
}

@JS('immutable.isList')
external bool isImmutableList(Object? object);

@JS('immutable.isOrderedMap')
external bool isImmutableMap(Object? object);

/// Converts [list], which may be either a JavaScript `Array` or an
/// [ImmutableList], into a Dart [List].
List<Object?> jsToDartList(Object? list) =>
isImmutableMap(list) ? (list as ImmutableList).toArray() : list as List;

/// Converts a Dart map into an equivalent [ImmutableMap].
ImmutableMap dartMapToImmutableMap(Map<Object, Object?> dartMap) {
var immutableMap = ImmutableMap().asMutable();
for (var entry in dartMap.entries) {
immutableMap = immutableMap.set(entry.key, entry.value);
}
return immutableMap.asImmutable();
}

/// Converts an [ImmutableMap] into an equivalent Dart map.
Map<Object, Object?> immutableMapToDartMap(ImmutableMap immutableMap) {
var dartMap = <Object, Object?>{};
immutableMap.forEach(allowInterop((value, key, _) {
dartMap[key] = value;
}));
return dartMap;
}
2 changes: 1 addition & 1 deletion lib/src/node/legacy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ List<AsyncCallable> _parseFunctions(RenderOptions options, DateTime start,
jsForEach(functions, (signature, callback) {
Tuple2<String, ArgumentDeclaration> tuple;
try {
tuple = ScssParser(signature as String).parseSignature();
tuple = ScssParser(signature).parseSignature();
} on SassFormatException catch (error, stackTrace) {
throwWithTrace(
SassFormatException(
Expand Down
34 changes: 18 additions & 16 deletions lib/src/node/legacy/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,27 @@

import 'package:js/js.dart';

import '../reflection.dart';

@JS()
@anonymous
class Types {
external set Boolean(Function function);
external set Color(Function function);
external set List(Function function);
external set Map(Function function);
external set Null(Function function);
external set Number(Function function);
external set String(Function function);
external set Error(Function function);
external set Boolean(JSClass function);
external set Color(JSClass function);
external set List(JSClass function);
external set Map(JSClass function);
external set Null(JSClass function);
external set Number(JSClass function);
external set String(JSClass function);
external set Error(JSClass function);

external factory Types(
{Function? Boolean,
Function? Color,
Function? List,
Function? Map,
Function? Null,
Function? Number,
Function? String,
Function? Error});
{JSClass? Boolean,
JSClass? Color,
JSClass? List,
JSClass? Map,
JSClass? Null,
JSClass? Number,
JSClass? String,
JSClass? Error});
}
28 changes: 11 additions & 17 deletions lib/src/node/legacy/value/boolean.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,25 @@

import 'dart:js_util';

import 'package:js/js.dart';

import '../../../value.dart';
import '../../utils.dart';
import '../../reflection.dart';

/// The JS constructor for the `sass.types.Boolean` class.
/// The JS `sass.types.Boolean` class.
///
/// Unlike most other values, Node Sass booleans use the same representation as
/// Dart Sass booleans without an additional wrapper. However, they still have
/// to have a constructor injected into their inheritance chain so that
/// `instanceof` works properly.
final Function booleanConstructor = () {
var constructor = allowInterop(([dynamic _]) {
final JSClass legacyBooleanClass = () {
var jsClass = createJSClass('sass.types.Boolean', (dynamic _, [dynamic __]) {
throw "new sass.types.Boolean() isn't allowed.\n"
"Use sass.types.Boolean.TRUE or sass.types.Boolean.FALSE instead.";
});
injectSuperclass(sassTrue, constructor);
setClassName(sassTrue, "SassBoolean");
forwardToString(constructor);
setProperty(
getProperty(constructor, "prototype") as Object,
"getValue",
allowInteropCaptureThis(
(Object thisArg) => identical(thisArg, sassTrue)));
setProperty(constructor, "TRUE", sassTrue);
setProperty(constructor, "FALSE", sassFalse);
return constructor;

jsClass.defineMethod('getValue', (Object self) => identical(self, sassTrue));
setProperty(jsClass, "TRUE", sassTrue);
setProperty(jsClass, "FALSE", sassFalse);

getJSClass(sassTrue).injectSuperclass(jsClass);
return jsClass;
}();
Loading

0 comments on commit 97c51d6

Please sign in to comment.