Skip to content

Commit

Permalink
Use node_interop rather than manually declaring Node APIs (#1127)
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Oct 28, 2020
1 parent 300197c commit a9a3946
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 143 deletions.
134 changes: 34 additions & 100 deletions lib/src/io/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,16 @@ import 'dart:convert';
import 'dart:js_util';

import 'package:js/js.dart';
import 'package:node_interop/fs.dart';
import 'package:node_interop/node_interop.dart';
import 'package:node_interop/stream.dart';
import 'package:path/path.dart' as p;
import 'package:source_span/source_span.dart';
import 'package:watcher/watcher.dart';

import '../exception.dart';
import '../node/chokidar.dart';

@JS()
class _FS {
external Object readFileSync(String path, [String encoding]);
external void writeFileSync(String path, String data);
external bool existsSync(String path);
external void mkdirSync(String path);
external _Stat statSync(String path);
external void unlinkSync(String path);
external List<Object> readdirSync(String path);
}

@JS()
class _Stat {
external bool isFile();
external bool isDirectory();
external _Date get mtime;
}

@JS()
class _Date {
external int getTime();
}

@JS()
class _Stderr {
external void write(String text);
}

@JS()
class _Stdin {
external String read();

external void on(String event, void callback([Object object]));
}

@JS()
class _SystemError {
external String get message;
external String get code;
external String get syscall;
external String get path;
}

@JS()
class _Process {
external String get platform;
external Object get env;
external String cwd();
}

class FileSystemException {
final String message;
final String path;
Expand All @@ -74,7 +27,7 @@ class FileSystemException {
}

class Stderr {
final _Stderr _stderr;
final Writable _stderr;

Stderr(this._stderr);

Expand All @@ -87,12 +40,6 @@ class Stderr {
void flush() {}
}

@JS("fs")
external _FS get _fs;

@JS("process")
external _Process get _process;

String readFile(String path) {
// TODO(nweiz): explicitly decode the bytes as UTF-8 like we do in the VM when
// it doesn't cause a substantial performance degradation for large files. See
Expand All @@ -112,13 +59,13 @@ String readFile(String path) {

/// Wraps `fs.readFileSync` to throw a [FileSystemException].
Object _readFile(String path, [String encoding]) =>
_systemErrorToFileSystemException(() => _fs.readFileSync(path, encoding));
_systemErrorToFileSystemException(() => fs.readFileSync(path, encoding));

void writeFile(String path, String contents) =>
_systemErrorToFileSystemException(() => _fs.writeFileSync(path, contents));
_systemErrorToFileSystemException(() => fs.writeFileSync(path, contents));

void deleteFile(String path) =>
_systemErrorToFileSystemException(() => _fs.unlinkSync(path));
_systemErrorToFileSystemException(() => fs.unlinkSync(path));

Future<String> readStdin() async {
var completer = Completer<String>();
Expand All @@ -129,16 +76,16 @@ Future<String> readStdin() async {
});
// Node defaults all buffers to 'utf8'.
var sink = utf8.decoder.startChunkedConversion(innerSink);
_stdin.on('data', allowInterop(([chunk]) {
process.stdin.on('data', allowInterop(([Object chunk]) {
assert(chunk != null);
sink.add(chunk as List<int>);
}));
_stdin.on('end', allowInterop(([_]) {
process.stdin.on('end', allowInterop(([Object _]) {
// Callback for 'end' receives no args.
assert(_ == null);
sink.close();
}));
_stdin.on('error', allowInterop(([e]) {
process.stdin.on('error', allowInterop(([Object e]) {
assert(e != null);
stderr.writeln('Failed to read from stdin');
stderr.writeln(e);
Expand All @@ -148,7 +95,7 @@ Future<String> readStdin() async {
}

/// Cleans up a Node system error's message.
String _cleanErrorMessage(_SystemError error) {
String _cleanErrorMessage(JsSystemError error) {
// The error message is of the form "$code: $text, $syscall '$path'". We just
// want the text.
return error.message.substring("${error.code}: ".length,
Expand All @@ -161,12 +108,12 @@ bool fileExists(String path) {
// whether the entity in question is a file or a directory. Since false
// negatives are much more common than false positives, it works out in our
// favor to check this first.
if (!_fs.existsSync(path)) return false;
if (!fs.existsSync(path)) return false;

try {
return _fs.statSync(path).isFile();
return fs.statSync(path).isFile();
} catch (error) {
var systemError = error as _SystemError;
var systemError = error as JsSystemError;
if (systemError.code == 'ENOENT') return false;
rethrow;
}
Expand All @@ -179,12 +126,12 @@ bool dirExists(String path) {
// whether the entity in question is a file or a directory. Since false
// negatives are much more common than false positives, it works out in our
// favor to check this first.
if (!_fs.existsSync(path)) return false;
if (!fs.existsSync(path)) return false;

try {
return _fs.statSync(path).isDirectory();
return fs.statSync(path).isDirectory();
} catch (error) {
var systemError = error as _SystemError;
var systemError = error as JsSystemError;
if (systemError.code == 'ENOENT') return false;
rethrow;
}
Expand All @@ -194,27 +141,27 @@ bool dirExists(String path) {
void ensureDir(String path) {
return _systemErrorToFileSystemException(() {
try {
_fs.mkdirSync(path);
fs.mkdirSync(path);
} catch (error) {
var systemError = error as _SystemError;
var systemError = error as JsSystemError;
if (systemError.code == 'EEXIST') return;
if (systemError.code != 'ENOENT') rethrow;
ensureDir(p.dirname(path));
_fs.mkdirSync(path);
fs.mkdirSync(path);
}
});
}

Iterable<String> listDir(String path, {bool recursive = false}) {
return _systemErrorToFileSystemException(() {
if (!recursive) {
return _fs
return fs
.readdirSync(path)
.map((child) => p.join(path, child as String))
.where((child) => !dirExists(child));
} else {
Iterable<String> list(String parent) =>
_fs.readdirSync(parent).expand((child) {
fs.readdirSync(parent).expand((child) {
var path = p.join(parent, child as String);
return dirExists(path) ? list(path) : [path];
});
Expand All @@ -225,55 +172,42 @@ Iterable<String> listDir(String path, {bool recursive = false}) {
}

DateTime modificationTime(String path) =>
_systemErrorToFileSystemException(() => DateTime.fromMillisecondsSinceEpoch(
_fs.statSync(path).mtime.getTime()));
_systemErrorToFileSystemException(() =>
DateTime.fromMillisecondsSinceEpoch(fs.statSync(path).mtime.getTime()));

String getEnvironmentVariable(String name) =>
getProperty(_process.env, name) as String;
getProperty(process.env, name) as String;

/// Runs callback and converts any [_SystemError]s it throws into
/// Runs callback and converts any [JsSystemError]s it throws into
/// [FileSystemException]s.
T _systemErrorToFileSystemException<T>(T callback()) {
try {
return callback();
} catch (error) {
var systemError = error as _SystemError;
var systemError = error as JsSystemError;
throw FileSystemException._(
_cleanErrorMessage(systemError), systemError.path);
}
}

@JS("process.stderr")
external _Stderr get _stderr;

final stderr = Stderr(_stderr);
final stderr = Stderr(process.stderr);

@JS("process.stdin")
external _Stdin get _stdin;
bool get hasTerminal => process.stdout.isTTY ?? false;

bool get hasTerminal => _hasTerminal ?? false;
bool get isWindows => process.platform == 'win32';

bool get isWindows => _process.platform == 'win32';

bool get isMacOS => _process.platform == 'darwin';
bool get isMacOS => process.platform == 'darwin';

bool get isNode => true;

// Node seems to support ANSI escapes on all terminals.
bool get supportsAnsiEscapes => hasTerminal;

String get currentPath => _process.cwd();

@JS("process.stdout.isTTY")
external bool get _hasTerminal;
String get currentPath => process.cwd();

@JS("process.exitCode")
external int get exitCode;
int get exitCode => process.exitCode;

// TODO(nweiz): remove this ignore when dart-lang/sdk#39250 is fixed.
// ignore: inference_failure_on_function_return_type
@JS("process.exitCode")
external set exitCode(int code);
set exitCode(int code) => process.exitCode = code;

Future<Stream<WatchEvent>> watchDir(String path, {bool poll = false}) {
var watcher = chokidar.watch(
Expand Down
18 changes: 9 additions & 9 deletions lib/src/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'dart:js_util';
import 'dart:typed_data';

import 'package:js/js.dart';
import 'package:node_interop/js.dart';
import 'package:path/path.dart' as p;
import 'package:tuple/tuple.dart';

Expand All @@ -17,7 +18,6 @@ import 'compile.dart';
import 'exception.dart';
import 'io.dart';
import 'importer/node.dart';
import 'node/error.dart';
import 'node/exports.dart';
import 'node/function.dart';
import 'node/render_context.dart';
Expand Down Expand Up @@ -65,13 +65,13 @@ void main() {
///
/// [render]: https://github.com/sass/node-sass#options
void _render(
RenderOptions options, void callback(JSError error, RenderResult result)) {
RenderOptions options, void callback(JsError error, RenderResult result)) {
if (options.fiber != null) {
options.fiber.call(allowInterop(() {
try {
callback(null, _renderSync(options));
} catch (error) {
callback(error as JSError, null);
callback(error as JsError, null);
}
return null;
})).run();
Expand Down Expand Up @@ -166,8 +166,8 @@ RenderResult _renderSync(RenderOptions options) {
throw "unreachable";
}

/// Converts an exception to a [JSError].
JSError _wrapException(Object exception) {
/// Converts an exception to a [JsError].
JsError _wrapException(Object exception) {
if (exception is SassException) {
return _newRenderError(exception.toString().replaceFirst("Error: ", ""),
line: exception.span.start.line + 1,
Expand All @@ -177,7 +177,7 @@ JSError _wrapException(Object exception) {
: p.fromUri(exception.span.sourceUrl),
status: 1);
} else {
return JSError(exception.toString());
return JsError(exception.toString());
}
}

Expand Down Expand Up @@ -388,11 +388,11 @@ bool _enableSourceMaps(RenderOptions options) =>
options.sourceMap is String ||
(isTruthy(options.sourceMap) && options.outFile != null);

/// Creates a [JSError] with the given fields added to it so it acts like a Node
/// Creates a [JsError] with the given fields added to it so it acts like a Node
/// Sass error.
JSError _newRenderError(String message,
JsError _newRenderError(String message,
{int line, int column, String file, int status}) {
var error = JSError(message);
var error = JsError(message);
setProperty(error, 'formatted', 'Error: $message');
if (line != null) setProperty(error, 'line', line);
if (column != null) setProperty(error, 'column', column);
Expand Down
11 changes: 0 additions & 11 deletions lib/src/node/error.dart

This file was deleted.

1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies:
cli_repl: ">=0.1.3 <0.3.0"
collection: "^1.8.0"
meta: "^1.1.7"
node_interop: "^1.1.0"
js: "^0.6.0"
package_resolver: "^1.0.0"
path: "^1.6.0"
Expand Down
9 changes: 0 additions & 9 deletions test/node_api/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import 'package:js/js.dart';
import 'package:path/path.dart' as p;

export 'package:sass/src/node/error.dart';
export 'package:sass/src/node/importer_result.dart';
export 'package:sass/src/node/render_context.dart';
export 'package:sass/src/node/render_options.dart';
Expand All @@ -24,11 +23,6 @@ final sass = _requireSass(p.absolute("build/npm/sass.dart"));
/// The Fiber class.
final fiber = _requireFiber("fibers");

/// A `null` that's guaranteed to be represented by JavaScript's `undefined`
/// value, not by `null`.
@JS()
external Object get undefined;

/// A `null` that's guaranteed to be represented by JavaScript's `null` value,
/// not by `undefined`.
///
Expand All @@ -39,9 +33,6 @@ final Object jsNull = _eval("null");
@JS("eval")
external Object _eval(String js);

@JS("process.chdir")
external void chdir(String directory);

@JS("require")
external Sass _requireSass(String path);

Expand Down

0 comments on commit a9a3946

Please sign in to comment.