From 13d0820c377df6508cd3d723ccc27ccad104694a Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 15 Mar 2019 12:50:48 -0700 Subject: [PATCH 01/27] Release 1.17.3 (#622) --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index c0d4cc5c..4b139182 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.17.3-dev +version: 1.17.3 description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass From bc5e3c4f8f220cc4f9aefa79d3d6354ffdaeffb2 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 18 Mar 2019 16:50:23 -0700 Subject: [PATCH 02/27] Properly parse CR, CR LF, and FF as newlines everywhere (#626) Closes #623 --- CHANGELOG.md | 5 +++++ lib/src/parse/sass.dart | 25 +++++++++++++++++++++++-- lib/src/parse/scss.dart | 10 ++++++++++ pubspec.yaml | 2 +- test/output_test.dart | 26 ++++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 test/output_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index bcbc8d60..1157f58a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.17.4 + +* Consistently parse U+000C FORM FEED, U+000D CARRIAGE RETURN, and sequences of + U+000D CARRIAGE RETURN followed by U+000A LINE FEED as individual newlines. + ## 1.17.3 * Fix an edge case where slash-separated numbers were written to the stylesheet diff --git a/lib/src/parse/sass.dart b/lib/src/parse/sass.dart index 6df58fc4..fe5aebbe 100644 --- a/lib/src/parse/sass.dart +++ b/lib/src/parse/sass.dart @@ -155,6 +155,7 @@ class SassParser extends StylesheetParser { // Ignore empty lines. case $cr: case $lf: + case $ff: return null; case $dollar: @@ -282,8 +283,8 @@ class SassParser extends StylesheetParser { if (_peekIndentation() <= parentIndentation) break; // Preserve empty lines. - while (isNewline(scanner.peekChar(1))) { - scanner.readChar(); + while (_lookingAtDoubleNewline()) { + _expectNewline(); buffer.writeln(); buffer.write(" *"); } @@ -315,7 +316,12 @@ class SassParser extends StylesheetParser { case $semicolon: scanner.error("semicolons aren't allowed in the indented syntax."); return; + case $cr: + scanner.readChar(); + if (scanner.peekChar() == $lf) scanner.readChar(); + return; case $lf: + case $ff: scanner.readChar(); return; default: @@ -323,6 +329,21 @@ class SassParser extends StylesheetParser { } } + /// Returns whether the scanner is immediately before *two* newlines. + bool _lookingAtDoubleNewline() { + switch (scanner.peekChar()) { + case $cr: + var nextChar = scanner.peekChar(1); + if (nextChar == $lf) return isNewline(scanner.peekChar(2)); + return nextChar == $cr || nextChar == $ff; + case $lf: + case $ff: + return isNewline(scanner.peekChar(1)); + default: + return false; + } + } + /// As long as the scanner's position is indented beneath the starting line, /// runs [body] to consume the next statement. void _whileIndentedLower(void body()) { diff --git a/lib/src/parse/scss.dart b/lib/src/parse/scss.dart index cd7cb6c6..37cbd7fe 100644 --- a/lib/src/parse/scss.dart +++ b/lib/src/parse/scss.dart @@ -184,6 +184,16 @@ class ScssParser extends StylesheetParser { buffer.writeCharCode(scanner.readChar()); return LoudComment(buffer.interpolation(scanner.spanFrom(start))); + case $cr: + scanner.readChar(); + if (scanner.peekChar() != $lf) buffer.writeCharCode($lf); + break; + + case $ff: + scanner.readChar(); + buffer.writeCharCode($lf); + break; + default: buffer.writeCharCode(scanner.readChar()); break; diff --git a/pubspec.yaml b/pubspec.yaml index 4b139182..35c75aa2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.17.3 +version: 1.17.4-dev description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass diff --git a/test/output_test.dart b/test/output_test.dart new file mode 100644 index 00000000..be513451 --- /dev/null +++ b/test/output_test.dart @@ -0,0 +1,26 @@ +// Copyright 2019 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. + +// Almost all CSS output tests should go in sass-spec rather than here. This +// just covers tests that explicitly validate out that's considered too +// implementation-specific to verify in sass-spec. + +import 'package:test/test.dart'; + +import 'package:sass/sass.dart'; + +void main() { + // Regression test for sass/dart-sass#623. This needs to be tested here + // because sass-spec normalizes CR LF newlines. + group("normalizes newlines in a loud comment", () { + test("in SCSS", () { + expect(compileString("/* foo\r\n * bar */"), equals("/* foo\n * bar */")); + }); + + test("in Sass", () { + expect(compileString("/*\r\n foo\r\n bar", syntax: Syntax.sass), + equals("/* foo\n * bar */")); + }); + }); +} From 9999835df0900b81b06019cc6008b522e3d34dae Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 3 Apr 2019 01:07:12 -0700 Subject: [PATCH 03/27] Add a sass.types.Error constructor to the JS API (#627) Closes #590 --- CHANGELOG.md | 5 +++++ lib/src/node.dart | 3 ++- lib/src/node/types.dart | 4 +++- lib/src/node/utils.dart | 4 ++-- lib/src/node/value.dart | 4 ++++ package.json | 1 + pubspec.yaml | 2 +- test/node_api/api.dart | 1 + test/node_api/function_test.dart | 23 +++++++++++++++++++++++ 9 files changed, 42 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1157f58a..e4f16b81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ * Consistently parse U+000C FORM FEED, U+000D CARRIAGE RETURN, and sequences of U+000D CARRIAGE RETURN followed by U+000A LINE FEED as individual newlines. +### JavaScript API + +* Add a `sass.types.Error` constructor as an alias for `Error`. This makes our + custom function API compatible with Node Sass's. + ## 1.17.3 * Fix an edge case where slash-separated numbers were written to the stylesheet diff --git a/lib/src/node.dart b/lib/src/node.dart index 482c8720..ff8245cc 100644 --- a/lib/src/node.dart +++ b/lib/src/node.dart @@ -56,7 +56,8 @@ void main() { Map: mapConstructor, Null: nullConstructor, Number: numberConstructor, - String: stringConstructor); + String: stringConstructor, + Error: jsErrorConstructor); } /// Converts Sass to CSS. diff --git a/lib/src/node/types.dart b/lib/src/node/types.dart index 143bd40b..07f05b6e 100644 --- a/lib/src/node/types.dart +++ b/lib/src/node/types.dart @@ -14,6 +14,8 @@ class Types { external set Null(function); external set Number(function); external set String(function); + external set Error(function); - external factory Types({Boolean, Color, List, Map, Null, Number, String}); + external factory Types( + {Boolean, Color, List, Map, Null, Number, String, Error}); } diff --git a/lib/src/node/utils.dart b/lib/src/node/utils.dart index e89751d4..38074ed6 100644 --- a/lib/src/node/utils.dart +++ b/lib/src/node/utils.dart @@ -34,10 +34,10 @@ bool isUndefined(value) => _isUndefined.call(value) as bool; final _isUndefined = JSFunction("value", "return value === undefined;"); @JS("Error") -external Function get _JSError; +external Function get jsErrorConstructor; /// Returns whether [value] is a JS Error object. -bool isJSError(value) => instanceof(value, _JSError); +bool isJSError(value) => instanceof(value, jsErrorConstructor); /// Invokes [function] with [thisArg] as `this`. Object call2(JSFunction function, Object thisArg, Object arg1, Object arg2) => diff --git a/lib/src/node/value.dart b/lib/src/node/value.dart index c3b5da41..156aa419 100644 --- a/lib/src/node/value.dart +++ b/lib/src/node/value.dart @@ -5,6 +5,7 @@ import 'dart:js_util'; import '../value.dart'; +import 'utils.dart'; import 'value/color.dart'; import 'value/list.dart'; import 'value/map.dart'; @@ -20,11 +21,14 @@ export 'value/number.dart'; export 'value/string.dart'; /// Unwraps a value wrapped with [wrapValue]. +/// +/// If [object] is a JS error, throws it. Value unwrapValue(object) { if (object != null) { if (object is Value) return object; var value = getProperty(object, 'dartValue'); if (value != null && value is Value) return value; + if (isJSError(object)) throw object; } throw "$object must be a Sass value type."; } diff --git a/package.json b/package.json index b13e2424..4732234f 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "install dependencies used for testing the Node API." ], "devDependencies": { + "node-sass": "^4.11.0", "chokidar": "^2.0.0", "fibers": ">=1.0.0 <4.0.0", "intercept-stdout": "^0.1.2" diff --git a/pubspec.yaml b/pubspec.yaml index 35c75aa2..38d4f6df 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.17.4-dev +version: 1.17.4 description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass diff --git a/test/node_api/api.dart b/test/node_api/api.dart index a75bb41c..35d2889d 100644 --- a/test/node_api/api.dart +++ b/test/node_api/api.dart @@ -75,6 +75,7 @@ class SassTypes { external NodeSassNullClass get Null; external Function get Number; external Function get String; + external Function get Error; } @JS() diff --git a/test/node_api/function_test.dart b/test/node_api/function_test.dart index f84bf973..1d4a3eeb 100644 --- a/test/node_api/function_test.dart +++ b/test/node_api/function_test.dart @@ -195,6 +195,16 @@ void main() { expect(error.toString(), contains('aw beans')); }); + test("reports a synchronous sass.types.Error", () async { + var error = await renderError(RenderOptions( + data: "a {b: foo()}", + functions: jsify({ + "foo": allowInterop( + (_) => callConstructor(sass.types.Error, ["aw beans"])) + }))); + expect(error.toString(), contains('aw beans')); + }); + test("reports an asynchronous error", () async { var error = await renderError(RenderOptions( data: "a {b: foo()}", @@ -208,6 +218,19 @@ void main() { expect(error.toString(), contains('aw beans')); }); + test("reports an asynchronous sass.types.Error", () async { + var error = await renderError(RenderOptions( + data: "a {b: foo()}", + functions: jsify({ + "foo": allowInterop((done) { + Future.delayed(Duration.zero).then((_) { + done(callConstructor(sass.types.Error, ["aw beans"])); + }); + }) + }))); + expect(error.toString(), contains('aw beans')); + }); + test("reports a null return", () async { var error = await renderError(RenderOptions( data: "a {b: foo()}", From 8c9412b5205745b415da64ec636da79c8df5c6b0 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 3 Apr 2019 13:57:52 -0700 Subject: [PATCH 04/27] Fix importing files relative to "package:" imports (#638) The PackageImporter wasn't accepting paths relative to its canonicalized outputs as inputs. Closes #631 --- CHANGELOG.md | 11 +++++++++++ lib/src/importer/async.dart | 6 ++++-- lib/src/importer/package.dart | 1 + pubspec.yaml | 2 +- test/dart_api_test.dart | 16 ++++++++++++++++ 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4f16b81..460c76d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.17.5 + +* Fix importing files relative to `package:`-imported files. + +### Dart API + +* Explicitly require that importers' `canonicalize()` methods be able to take + paths relative to their outputs as valid inputs. This isn't considered a + breaking change because the importer infrastructure already required this in + practice. + ## 1.17.4 * Consistently parse U+000C FORM FEED, U+000D CARRIAGE RETURN, and sequences of diff --git a/lib/src/importer/async.dart b/lib/src/importer/async.dart index 1aad74fd..b92af2f7 100644 --- a/lib/src/importer/async.dart +++ b/lib/src/importer/async.dart @@ -68,8 +68,10 @@ abstract class AsyncImporter { /// /// If no stylesheets are found, the importer should return `null`. /// - /// Sass assumes that calling [canonicalize] multiple times with the same URL - /// will return the same result. + /// Calling [canonicalize] multiple times with the same URL must return the + /// same result. Calling [canonicalize] with a URL returned by [canonicalize] + /// must return that URL. Calling [canonicalize] with a URL relative to one + /// returned by [canonicalize] must return a meaningful result. FutureOr canonicalize(Uri url); /// Loads the Sass text for the given [url], or returns `null` if diff --git a/lib/src/importer/package.dart b/lib/src/importer/package.dart index 721909ba..9d92a588 100644 --- a/lib/src/importer/package.dart +++ b/lib/src/importer/package.dart @@ -29,6 +29,7 @@ class PackageImporter extends Importer { PackageImporter(this._packageResolver); Uri canonicalize(Uri url) { + if (url.scheme == 'file') return _filesystemImporter.canonicalize(url); if (url.scheme != 'package') return null; var resolved = _packageResolver.resolveUri(url); diff --git a/pubspec.yaml b/pubspec.yaml index 38d4f6df..938a624d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.17.4 +version: 1.17.5-dev description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass diff --git a/test/dart_api_test.dart b/test/dart_api_test.dart index a1c56d4a..75737617 100644 --- a/test/dart_api_test.dart +++ b/test/dart_api_test.dart @@ -100,6 +100,22 @@ main() { expect(css, equals("a {\n b: 3;\n}")); }); + test("can resolve relative paths in a package", () async { + await d.dir("subdir", [ + d.file("test.scss", "@import 'other'"), + d.file("_other.scss", "a {b: 1 + 2}"), + ]).create(); + + await d + .file("test.scss", '@import "package:fake_package/test";') + .create(); + var resolver = SyncPackageResolver.config( + {"fake_package": p.toUri(d.path('subdir'))}); + + var css = compile(d.path("test.scss"), packageResolver: resolver); + expect(css, equals("a {\n b: 3;\n}")); + }); + test("doesn't import a package URL from a missing package", () async { await d .file("test.scss", '@import "package:fake_package/test_aux";') From c2c9cedb8188b076904333dd5a91ef45d49f31e1 Mon Sep 17 00:00:00 2001 From: Jennifer Thakar Date: Wed, 3 Apr 2019 21:37:27 +0000 Subject: [PATCH 05/27] Extend RecursiveStatementVisitor for expressions (#639) This adds `RecursiveAstVisitor`, which extends `RecursiveStatementVisitor` to traverse expressions as well. --- lib/src/visitor/recursive_ast.dart | 80 ++++++++++++++++++++++++ lib/src/visitor/recursive_statement.dart | 2 + 2 files changed, 82 insertions(+) create mode 100644 lib/src/visitor/recursive_ast.dart diff --git a/lib/src/visitor/recursive_ast.dart b/lib/src/visitor/recursive_ast.dart new file mode 100644 index 00000000..5c4f11be --- /dev/null +++ b/lib/src/visitor/recursive_ast.dart @@ -0,0 +1,80 @@ +// Copyright 2019 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 '../ast/sass.dart'; +import 'interface/expression.dart'; +import 'recursive_statement.dart'; + +/// A visitor that recursively traverses each statement and expression in a Sass +/// AST. +/// +/// This extends [RecursiveStatementVisitor] to traverse each expression in +/// addition to each statement. +/// +/// The default implementation of the visit methods all return `null`. +abstract class RecursiveAstVisitor extends RecursiveStatementVisitor + implements ExpressionVisitor { + void visitExpression(Expression expression) { + expression.accept(this); + } + + T visitBinaryOperationExpression(BinaryOperationExpression node) { + node.left.accept(this); + node.right.accept(this); + return null; + } + + T visitBooleanExpression(BooleanExpression node) => null; + + T visitColorExpression(ColorExpression node) => null; + + T visitFunctionExpression(FunctionExpression node) { + visitInterpolation(node.name); + visitArgumentInvocation(node.arguments); + return null; + } + + T visitIfExpression(IfExpression node) { + visitArgumentInvocation(node.arguments); + return null; + } + + T visitListExpression(ListExpression node) { + for (var item in node.contents) { + item.accept(this); + } + return null; + } + + T visitMapExpression(MapExpression node) { + for (var pair in node.pairs) { + pair.item1.accept(this); + pair.item2.accept(this); + } + return null; + } + + T visitNullExpression(NullExpression node) => null; + + T visitNumberExpression(NumberExpression node) => null; + + T visitParenthesizedExpression(ParenthesizedExpression node) => + node.expression.accept(this); + + T visitSelectorExpression(SelectorExpression node) => null; + + T visitStringExpression(StringExpression node) { + visitInterpolation(node.text); + return null; + } + + T visitUnaryOperationExpression(UnaryOperationExpression node) => + node.operand.accept(this); + + T visitUseRule(UseRule node) => null; + + T visitValueExpression(ValueExpression node) => null; + + T visitVariableExpression(VariableExpression node) => null; +} diff --git a/lib/src/visitor/recursive_statement.dart b/lib/src/visitor/recursive_statement.dart index b6fbba8b..13ddc730 100644 --- a/lib/src/visitor/recursive_statement.dart +++ b/lib/src/visitor/recursive_statement.dart @@ -137,6 +137,8 @@ abstract class RecursiveStatementVisitor implements StatementVisitor { return visitChildren(node); } + T visitUseRule(UseRule node) => null; + T visitVariableDeclaration(VariableDeclaration node) { visitExpression(node.expression); return null; From 9621979b71c6e47213bd1b894a0ef2ab7138a50b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 3 Apr 2019 15:17:17 -0700 Subject: [PATCH 06/27] Update the Heroku auth token (#640) The old token appears to have been rendered invalid somehow. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a3be6a8c..b84d4e79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -151,7 +151,7 @@ jobs: - if: *deploy-if env: # HEROKU_TOKEN="..." - - secure: "jF3TCL+k6xdXWfEh54K6KrZ3w0oljUpP0uy9Hx0BIM5gaqG6fUijUnNGCkWDZxufEpl68mGxNRNMB2Mv++UXHiT7ChFx8zZqEyc5FzhIu/nVO3CP3Sek7fuktYidtUvqJ6eHkI15990dWkUoE+TTXTc4/Z9vv1Lt3JX8Ni5VApGCmcLjRwW52EkCC49xo7cWE8/wBEm2ntOivLBIXEKq6hpncXTO4H5KYt042WAJ+MPmQZYE1ENJAObXWrGituRCT6DQnIJuTodOn24SU1KJuvEtfskEJQUajIIQw29uvmu4TP7dgaJw8QBt+hdgcCYrMhoq3RTNmD+vitLRloG4QMWHFYhzONVZ8S3vAhKeolL7nnIz150FpLVQiddSLsdGomqjCfYEJN7TVrwvunGgHxygcGBcq2AiydnxREnlW9Rj6m6g6TVlhdX7JtyePDQN7xEDdZF1UbGMA6CDjzFsi0GY2WNLSCAANUOXmst0kDIFHGc6WkIUXMIbfmkUZADKzF/JDtnEQqtU8Qxc8JfW6ODXqC/fowE3q4cr8NnJMtclyIL/DsWSx2ph3vUr/VH5MWXd4MDJ6ZRnSJHaY2E0IYcKU2JEpA8r7xrFK/+/B9qCMPnoegRFfuN+zHM9b84rNzaF8fmuWuMVKzncw/TvXttRFqoZVS2Ej1EfLY3SA3M=" + - secure: "JUBfLfJr+5RIvxkk+1qqtyJxaHq2A9x78G9L9bUTjBD3C7XJOmJtt8u+pAgperL+fIfrlcAVzmYTyRGLj+0PL4VcxMTrmn604FIn+ffcgatylYKtPdpJq5dnXQ4U0+ROaZnq1mPZeUI7OHwRlVcQStIWkPxk7Eno+ZqxFyziLZ4wxo7l809iCAbhJttCfWPupyHX75cHhBMFaPEk9LYUrxQURyE0GKMQgDKHY/ehe87gyuKt3o79gi7/E0chQApv1jeua4xz5tyNBNQH/nwuJmmBCab/IdgTLn2vj4qjT1ENeB5PINCfFOT98IPVREOLbY+jiGRVwQFJbC55mOBQH21mfIt/XLhcjYHe80X4PyYhtJJ2GDadcyrzppZmHpEDHzfR8B29LEKhNM1VfUTZ8I9+TudXW8uMjtqCsXyi8bKZLsuZNNSlWVh1qIR+FXMDTrYNOnTcvgzv5yi6fbD10Uf8k1G0pHtKQiFainWatmJhNIMtGoYe7LRAB0Rj7OGWDMv/PHy/+Z7BKIj3b0LefVN1xpeuy3mMhMq9g5Q8HI8yk37DNmZQ9kwgHpIUk/t2xAdwzZ0XMSDFW9iHV48/iHwi0t5M2RCFRnI8ZaUNU5Z8QLUPHnazCucIvR4N8ns1yFwDgNQ5CzlBFrV70EwgqZhjjToAOhnIXpHMWr3AVAw=" install: skip script: skip deploy: From 495c6f3163811c14129bcd6c3ab3a8f376149493 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 4 Apr 2019 12:17:36 -0700 Subject: [PATCH 07/27] Only claim "package:" URLs are unsupported on Node (#637) Closes #630 --- CHANGELOG.md | 2 ++ lib/src/visitor/async_evaluate.dart | 3 ++- lib/src/visitor/evaluate.dart | 5 +++-- test/cli/dart/errors_test.dart | 18 ++++++++++++++++++ test/cli/node/errors_test.dart | 18 ++++++++++++++++++ test/cli/shared/errors.dart | 17 ----------------- 6 files changed, 43 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 460c76d8..0401866f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ * Fix importing files relative to `package:`-imported files. +* Don't claim that "package:" URLs aren't supported when they actually are. + ### Dart API * Explicitly require that importers' `canonicalize()` methods be able to take diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index cebb42db..74f0839d 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -27,6 +27,7 @@ import '../extend/extender.dart'; import '../importer.dart'; import '../importer/node.dart'; import '../importer/utils.dart'; +import '../io.dart'; import '../logger.dart'; import '../parse/keyframe_selector.dart'; import '../syntax.dart'; @@ -851,7 +852,7 @@ class _EvaluateVisitor if (tuple != null) return tuple; } - if (url.startsWith('package:')) { + if (url.startsWith('package:') && isNode) { // Special-case this error message, since it's tripped people up in the // past. throw "\"package:\" URLs aren't supported on this platform."; diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 5c327cb2..5ca9b4a9 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/synchronize.dart for details. // -// Checksum: be2d44a3dc50defd98446d806b4c21bfe54a4ba9 +// Checksum: e7642c8d4c838f0afe94b882794e81b0937ba1a1 // // ignore_for_file: unused_import @@ -36,6 +36,7 @@ import '../extend/extender.dart'; import '../importer.dart'; import '../importer/node.dart'; import '../importer/utils.dart'; +import '../io.dart'; import '../logger.dart'; import '../parse/keyframe_selector.dart'; import '../syntax.dart'; @@ -851,7 +852,7 @@ class _EvaluateVisitor if (tuple != null) return tuple; } - if (url.startsWith('package:')) { + if (url.startsWith('package:') && isNode) { // Special-case this error message, since it's tripped people up in the // past. throw "\"package:\" URLs aren't supported on this platform."; diff --git a/test/cli/dart/errors_test.dart b/test/cli/dart/errors_test.dart index db672602..478d2129 100644 --- a/test/cli/dart/errors_test.dart +++ b/test/cli/dart/errors_test.dart @@ -5,6 +5,7 @@ @TestOn('vm') import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; import '../dart_test.dart'; import '../shared/errors.dart'; @@ -12,4 +13,21 @@ import '../shared/errors.dart'; void main() { setUpAll(ensureSnapshotUpToDate); sharedTests(runSass); + + test("for package urls", () async { + await d.file("test.scss", "@import 'package:nope/test';").create(); + + var sass = await runSass(["--no-unicode", "test.scss"]); + expect( + sass.stderr, + emitsInOrder([ + "Error: Can't find stylesheet to import.", + " ,", + "1 | @import 'package:nope/test';", + " | ^^^^^^^^^^^^^^^^^^^", + " '", + " test.scss 1:9 root stylesheet" + ])); + await sass.shouldExit(65); + }); } diff --git a/test/cli/node/errors_test.dart b/test/cli/node/errors_test.dart index fda63b70..87220b35 100644 --- a/test/cli/node/errors_test.dart +++ b/test/cli/node/errors_test.dart @@ -6,6 +6,7 @@ @Tags(['node']) import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; import '../../ensure_npm_package.dart'; import '../node_test.dart'; @@ -14,4 +15,21 @@ import '../shared/errors.dart'; void main() { setUpAll(ensureNpmPackage); sharedTests(runSass); + + test("for package urls", () async { + await d.file("test.scss", "@import 'package:nope/test';").create(); + + var sass = await runSass(["--no-unicode", "test.scss"]); + expect( + sass.stderr, + emitsInOrder([ + "Error: \"package:\" URLs aren't supported on this platform.", + " ,", + "1 | @import 'package:nope/test';", + " | ^^^^^^^^^^^^^^^^^^^", + " '", + " test.scss 1:9 root stylesheet" + ])); + await sass.shouldExit(65); + }); } diff --git a/test/cli/shared/errors.dart b/test/cli/shared/errors.dart index 051bb670..2d75e9ec 100644 --- a/test/cli/shared/errors.dart +++ b/test/cli/shared/errors.dart @@ -113,21 +113,4 @@ void sharedTests(Future runSass(Iterable arguments)) { expect(sass.stderr, emitsThrough(contains("\.dart"))); await sass.shouldExit(65); }); - - test("for package urls", () async { - await d.file("test.scss", "@import 'package:nope/test';").create(); - - var sass = await runSass(["--no-unicode", "test.scss"]); - expect( - sass.stderr, - emitsInOrder([ - "Error: \"package:\" URLs aren't supported on this platform.", - " ,", - "1 | @import 'package:nope/test';", - " | ^^^^^^^^^^^^^^^^^^^", - " '", - " test.scss 1:9 root stylesheet" - ])); - await sass.shouldExit(65); - }); } From 59800ad1f98eedc3de4030aa1a78f90818ae874f Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 4 Apr 2019 12:18:02 -0700 Subject: [PATCH 08/27] Don't recursively list directories in realCasePath() (#641) We only need to list the path's immediate parent directory in order to find its real case. Closes #636 --- CHANGELOG.md | 3 +++ lib/src/executable/options.dart | 2 +- lib/src/io/interface.dart | 8 +++++--- lib/src/io/node.dart | 23 ++++++++++++++++------- lib/src/io/vm.dart | 9 +++++---- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0401866f..d324b2b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## 1.17.5 +* Avoid recursively listing directories when finding the canonical name of a + file on case-insensitive filesystems. + * Fix importing files relative to `package:`-imported files. * Don't claim that "package:" URLs aren't supported when they actually are. diff --git a/lib/src/executable/options.dart b/lib/src/executable/options.dart index 82ef17da..e92e40fb 100644 --- a/lib/src/executable/options.dart +++ b/lib/src/executable/options.dart @@ -356,7 +356,7 @@ class ExecutableOptions { /// [destination] directories. Map _listSourceDirectory(String source, String destination) { var map = {}; - for (var path in listDir(source)) { + for (var path in listDir(source, recursive: true)) { var basename = p.basename(path); if (basename.startsWith("_")) continue; diff --git a/lib/src/io/interface.dart b/lib/src/io/interface.dart index 16653d65..a41d3b14 100644 --- a/lib/src/io/interface.dart +++ b/lib/src/io/interface.dart @@ -79,9 +79,11 @@ bool dirExists(String path) => null; /// necessary. void ensureDir(String path) => null; -/// Recursively lists the files (not sub-directories) of the directory at -/// [path]. -Iterable listDir(String path) => null; +/// Lists the files (not sub-directories) in the directory at [path]. +/// +/// If [recursive] is `true`, this lists files in directories transitively +/// beneath [path] as well. +Iterable listDir(String path, {bool recursive = false}) => null; /// Returns the modification time of the file at [path]. DateTime modificationTime(String path) => null; diff --git a/lib/src/io/node.dart b/lib/src/io/node.dart index f7cec7bb..77558b1b 100644 --- a/lib/src/io/node.dart +++ b/lib/src/io/node.dart @@ -191,14 +191,23 @@ void ensureDir(String path) { }); } -Iterable listDir(String path) { - Iterable list(String parent) => - _fs.readdirSync(parent).expand((child) { - var path = p.join(parent, child as String); - return dirExists(path) ? listDir(path) : [path]; - }); +Iterable listDir(String path, {bool recursive = false}) { + return _systemErrorToFileSystemException(() { + if (!recursive) { + return _fs + .readdirSync(path) + .map((child) => p.join(path, child as String)) + .where((child) => !dirExists(child)); + } else { + Iterable list(String parent) => + _fs.readdirSync(parent).expand((child) { + var path = p.join(parent, child as String); + return dirExists(path) ? list(path) : [path]; + }); - return _systemErrorToFileSystemException(() => list(path)); + return list(path); + } + }); } DateTime modificationTime(String path) => diff --git a/lib/src/io/vm.dart b/lib/src/io/vm.dart index 849f7dc9..e439609c 100644 --- a/lib/src/io/vm.dart +++ b/lib/src/io/vm.dart @@ -71,10 +71,11 @@ bool dirExists(String path) => io.Directory(path).existsSync(); void ensureDir(String path) => io.Directory(path).createSync(recursive: true); -Iterable listDir(String path) => io.Directory(path) - .listSync(recursive: true) - .where((entity) => entity is io.File) - .map((entity) => entity.path); +Iterable listDir(String path, {bool recursive = false}) => + io.Directory(path) + .listSync(recursive: recursive) + .where((entity) => entity is io.File) + .map((entity) => entity.path); DateTime modificationTime(String path) { var stat = io.FileStat.statSync(path); From 3b3a43a8f9f9176bf87e95f255de5c8503b021f1 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 8 Apr 2019 14:49:08 -0700 Subject: [PATCH 09/27] Add a --charset flag and API option (#644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The automatic @charset adding is useful in general, but there are consistently cases where it trips up naΓ―ve downstream tools. This option makes it easier for users to control when it occurs. --- CHANGELOG.md | 13 +++- lib/sass.dart | 28 ++++++- lib/src/async_compile.dart | 18 +++-- lib/src/compile.dart | 20 +++-- lib/src/executable/compile_stylesheet.dart | 12 ++- lib/src/executable/options.dart | 7 ++ lib/src/visitor/serialize.dart | 8 +- pubspec.yaml | 2 +- test/cli/shared.dart | 87 ++++++++++++++++++++++ test/dart_api_test.dart | 40 ++++++++++ 10 files changed, 210 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d324b2b9..f45686b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.17.5 +## 1.18.0 * Avoid recursively listing directories when finding the canonical name of a file on case-insensitive filesystems. @@ -7,8 +7,19 @@ * Don't claim that "package:" URLs aren't supported when they actually are. +### Command-Line Interface + +* Add a `--no-charset` flag. If this flag is set, Sass will never emit a + `@charset` declaration or a byte-order mark, even if the CSS file contains + non-ASCII characters. + ### Dart API +* Add a `charset` option to `compile()`, `compileString()`, `compileAsync()` and + `compileStringAsync()`. If this option is set to `false`, Sass will never emit + a `@charset` declaration or a byte-order mark, even if the CSS file contains + non-ASCII characters. + * Explicitly require that importers' `canonicalize()` methods be able to take paths relative to their outputs as valid inputs. This isn't considered a breaking change because the importer infrastructure already required this in diff --git a/lib/sass.dart b/lib/sass.dart index 7967196a..db8e0c66 100644 --- a/lib/sass.dart +++ b/lib/sass.dart @@ -70,6 +70,13 @@ export 'src/visitor/serialize.dart' show OutputStyle; /// /// [`source_maps`]: https://pub.dartlang.org/packages/source_maps /// +/// If [charset] is `true`, this will include a `@charset` declaration or a +/// UTF-8 [byte-order mark][] if the stylesheet contains any non-ASCII +/// characters. Otherwise, it will never include a `@charset` declaration or a +/// byte-order mark. +/// +/// [byte-order mark]: https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 +/// /// This parameter is meant to be used as an out parameter, so that users who /// want access to the source map can get it. For example: /// @@ -87,7 +94,8 @@ String compile(String path, SyncPackageResolver packageResolver, Iterable functions, OutputStyle style, - void sourceMap(SingleMapping map)}) { + void sourceMap(SingleMapping map), + bool charset = true}) { logger ??= Logger.stderr(color: color); var result = c.compile(path, logger: logger, @@ -97,7 +105,8 @@ String compile(String path, packageResolver: packageResolver), functions: functions, style: style, - sourceMap: sourceMap != null); + sourceMap: sourceMap != null, + charset: charset); if (sourceMap != null) sourceMap(result.sourceMap); return result.css; } @@ -149,6 +158,13 @@ String compile(String path, /// /// [`source_maps`]: https://pub.dartlang.org/packages/source_maps /// +/// If [charset] is `true`, this will include a `@charset` declaration or a +/// UTF-8 [byte-order mark][] if the stylesheet contains any non-ASCII +/// characters. Otherwise, it will never include a `@charset` declaration or a +/// byte-order mark. +/// +/// [byte-order mark]: https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 +/// /// This parameter is meant to be used as an out parameter, so that users who /// want access to the source map can get it. For example: /// @@ -170,6 +186,7 @@ String compileString(String source, Importer importer, url, void sourceMap(SingleMapping map), + bool charset = true, @Deprecated("Use syntax instead.") bool indented = false}) { logger ??= Logger.stderr(color: color); var result = c.compileString(source, @@ -183,7 +200,8 @@ String compileString(String source, style: style, importer: importer, url: url, - sourceMap: sourceMap != null); + sourceMap: sourceMap != null, + charset: charset); if (sourceMap != null) sourceMap(result.sourceMap); return result.css; } @@ -233,6 +251,7 @@ Future compileStringAsync(String source, AsyncImporter importer, url, void sourceMap(SingleMapping map), + bool charset = true, @Deprecated("Use syntax instead.") bool indented = false}) async { logger ??= Logger.stderr(color: color); var result = await c.compileStringAsync(source, @@ -246,7 +265,8 @@ Future compileStringAsync(String source, style: style, importer: importer, url: url, - sourceMap: sourceMap != null); + sourceMap: sourceMap != null, + charset: charset); if (sourceMap != null) sourceMap(result.sourceMap); return result.css; } diff --git a/lib/src/async_compile.dart b/lib/src/async_compile.dart index 9559a334..bd1c1f9c 100644 --- a/lib/src/async_compile.dart +++ b/lib/src/async_compile.dart @@ -36,7 +36,8 @@ Future compileAsync(String path, bool useSpaces = true, int indentWidth, LineFeed lineFeed, - bool sourceMap = false}) async { + bool sourceMap = false, + bool charset = true}) async { // If the syntax is different than the importer would default to, we have to // parse the file manually and we can't store it in the cache. Stylesheet stylesheet; @@ -62,7 +63,8 @@ Future compileAsync(String path, useSpaces, indentWidth, lineFeed, - sourceMap); + sourceMap, + charset); } /// Like [compileStringAsync] in `lib/sass.dart`, but provides more options to @@ -84,7 +86,8 @@ Future compileStringAsync(String source, int indentWidth, LineFeed lineFeed, url, - bool sourceMap = false}) async { + bool sourceMap = false, + bool charset = true}) async { var stylesheet = Stylesheet.parse(source, syntax ?? Syntax.scss, url: url, logger: logger); @@ -99,7 +102,8 @@ Future compileStringAsync(String source, useSpaces, indentWidth, lineFeed, - sourceMap); + sourceMap, + charset); } /// Compiles [stylesheet] and returns its result. @@ -116,7 +120,8 @@ Future _compileStylesheet( bool useSpaces, int indentWidth, LineFeed lineFeed, - bool sourceMap) async { + bool sourceMap, + bool charset) async { var evaluateResult = await evaluateAsync(stylesheet, importCache: importCache, nodeImporter: nodeImporter, @@ -130,7 +135,8 @@ Future _compileStylesheet( useSpaces: useSpaces, indentWidth: indentWidth, lineFeed: lineFeed, - sourceMap: sourceMap); + sourceMap: sourceMap, + charset: charset); if (serializeResult.sourceMap != null && importCache != null) { // TODO(nweiz): Don't explicitly use a type parameter when dart-lang/sdk#25490 diff --git a/lib/src/compile.dart b/lib/src/compile.dart index 6d332461..35b042a8 100644 --- a/lib/src/compile.dart +++ b/lib/src/compile.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_compile.dart. // See tool/synchronize.dart for details. // -// Checksum: 2bb00947655b3add16335253802a82188d730595 +// Checksum: ea78ec4431055c1d222e52f4ea54a9659c4df11f // // ignore_for_file: unused_import @@ -45,7 +45,8 @@ CompileResult compile(String path, bool useSpaces = true, int indentWidth, LineFeed lineFeed, - bool sourceMap = false}) { + bool sourceMap = false, + bool charset = true}) { // If the syntax is different than the importer would default to, we have to // parse the file manually and we can't store it in the cache. Stylesheet stylesheet; @@ -71,7 +72,8 @@ CompileResult compile(String path, useSpaces, indentWidth, lineFeed, - sourceMap); + sourceMap, + charset); } /// Like [compileString] in `lib/sass.dart`, but provides more options to @@ -93,7 +95,8 @@ CompileResult compileString(String source, int indentWidth, LineFeed lineFeed, url, - bool sourceMap = false}) { + bool sourceMap = false, + bool charset = true}) { var stylesheet = Stylesheet.parse(source, syntax ?? Syntax.scss, url: url, logger: logger); @@ -108,7 +111,8 @@ CompileResult compileString(String source, useSpaces, indentWidth, lineFeed, - sourceMap); + sourceMap, + charset); } /// Compiles [stylesheet] and returns its result. @@ -125,7 +129,8 @@ CompileResult _compileStylesheet( bool useSpaces, int indentWidth, LineFeed lineFeed, - bool sourceMap) { + bool sourceMap, + bool charset) { var evaluateResult = evaluate(stylesheet, importCache: importCache, nodeImporter: nodeImporter, @@ -139,7 +144,8 @@ CompileResult _compileStylesheet( useSpaces: useSpaces, indentWidth: indentWidth, lineFeed: lineFeed, - sourceMap: sourceMap); + sourceMap: sourceMap, + charset: charset); if (serializeResult.sourceMap != null && importCache != null) { // TODO(nweiz): Don't explicitly use a type parameter when dart-lang/sdk#25490 diff --git a/lib/src/executable/compile_stylesheet.dart b/lib/src/executable/compile_stylesheet.dart index 5c82b097..4db0c08d 100644 --- a/lib/src/executable/compile_stylesheet.dart +++ b/lib/src/executable/compile_stylesheet.dart @@ -67,13 +67,15 @@ Future compileStylesheet(ExecutableOptions options, StylesheetGraph graph, importCache: importCache, importer: FilesystemImporter('.'), style: options.style, - sourceMap: options.emitSourceMap) + sourceMap: options.emitSourceMap, + charset: options.charset) : await compileAsync(source, syntax: syntax, logger: options.logger, importCache: importCache, style: options.style, - sourceMap: options.emitSourceMap); + sourceMap: options.emitSourceMap, + charset: options.charset); } else { result = source == null ? compileString(await readStdin(), @@ -82,13 +84,15 @@ Future compileStylesheet(ExecutableOptions options, StylesheetGraph graph, importCache: graph.importCache, importer: FilesystemImporter('.'), style: options.style, - sourceMap: options.emitSourceMap) + sourceMap: options.emitSourceMap, + charset: options.charset) : compile(source, syntax: syntax, logger: options.logger, importCache: graph.importCache, style: options.style, - sourceMap: options.emitSourceMap); + sourceMap: options.emitSourceMap, + charset: options.charset); } var css = result.css; diff --git a/lib/src/executable/options.dart b/lib/src/executable/options.dart index e92e40fb..c8363c8c 100644 --- a/lib/src/executable/options.dart +++ b/lib/src/executable/options.dart @@ -55,6 +55,9 @@ class ExecutableOptions { help: 'Output style.', allowed: ['expanded', 'compressed'], defaultsTo: 'expanded') + ..addFlag('charset', + help: 'Emit a @charset or BOM for CSS with non-ASCII characters.', + defaultsTo: true) ..addFlag('update', help: 'Only compile out-of-date stylesheets.', negatable: false); @@ -171,6 +174,10 @@ class ExecutableOptions { ? OutputStyle.compressed : OutputStyle.expanded; + /// Whether to include a `@charset` declaration or a BOM if the stylesheet + /// contains any non-ASCII characters. + bool get charset => _options['charset'] as bool; + /// The set of paths Sass in which should look for imported files. List get loadPaths => _options['load-path'] as List; diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index bd41af68..9ee003c2 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -39,13 +39,17 @@ import 'interface/value.dart'; /// /// If [sourceMap] is `true`, the returned [SerializeResult] will contain a /// source map indicating how the original Sass files map to the compiled CSS. +/// +/// If [charset] is `true`, this will include a `@charset` declaration or a BOM +/// if the stylesheet contains any non-ASCII characters. SerializeResult serialize(CssNode node, {OutputStyle style, bool inspect = false, bool useSpaces = true, int indentWidth, LineFeed lineFeed, - bool sourceMap = false}) { + bool sourceMap = false, + bool charset = true}) { indentWidth ??= 2; var visitor = _SerializeVisitor( style: style, @@ -57,7 +61,7 @@ SerializeResult serialize(CssNode node, node.accept(visitor); var css = visitor._buffer.toString(); String prefix; - if (css.codeUnits.any((codeUnit) => codeUnit > 0x7F)) { + if (charset && css.codeUnits.any((codeUnit) => codeUnit > 0x7F)) { if (style == OutputStyle.compressed) { prefix = '\uFEFF'; } else { diff --git a/pubspec.yaml b/pubspec.yaml index 938a624d..13e319fc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.17.5-dev +version: 1.18.0 description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass diff --git a/test/cli/shared.dart b/test/cli/shared.dart index 31b0e74c..d909c33c 100644 --- a/test/cli/shared.dart +++ b/test/cli/shared.dart @@ -3,6 +3,7 @@ // https://opensource.org/licenses/MIT. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as p; @@ -389,4 +390,90 @@ void sharedTests( await sass.shouldExit(0); }); }); + + group("with --charset", () { + test("doesn't emit @charset for a pure-ASCII stylesheet", () async { + await d.file("test.scss", "a {b: c}").create(); + + var sass = await runSass(["test.scss"]); + expect( + sass.stdout, + emitsInOrder([ + "a {", + " b: c;", + "}", + ])); + await sass.shouldExit(0); + }); + + test("emits @charset with expanded output", () async { + await d.file("test.scss", "a {b: πŸ‘­}").create(); + + var sass = await runSass(["test.scss"]); + expect( + sass.stdout, + emitsInOrder([ + "@charset \"UTF-8\";", + "a {", + " b: πŸ‘­;", + "}", + ])); + await sass.shouldExit(0); + }); + + test("emits a BOM with compressed output", () async { + await d.file("test.scss", "a {b: πŸ‘­}").create(); + + var sass = await runSass( + ["--no-source-map", "--style=compressed", "test.scss", "test.css"]); + await sass.shouldExit(0); + + // We can't verify this as a string because `dart:io` automatically trims + // the BOM. + var bomBytes = utf8.encode("\uFEFF"); + expect( + File(p.join(d.sandbox, "test.css")) + .readAsBytesSync() + .sublist(0, bomBytes.length), + equals(bomBytes)); + }); + }); + + group("with --no-charset", () { + test("doesn't emit @charset with expanded output", () async { + await d.file("test.scss", "a {b: πŸ‘­}").create(); + + var sass = await runSass(["--no-charset", "test.scss"]); + expect( + sass.stdout, + emitsInOrder([ + "a {", + " b: πŸ‘­;", + "}", + ])); + await sass.shouldExit(0); + }); + + test("doesn't emit a BOM with compressed output", () async { + await d.file("test.scss", "a {b: πŸ‘­}").create(); + + var sass = await runSass([ + "--no-charset", + "--no-source-map", + "--style=compressed", + "test.scss", + "test.css" + ]); + await sass.shouldExit(0); + + // We can't verify this as a string because `dart:io` automatically trims + // the BOM. + var bomBytes = utf8.encode("\uFEFF"); + expect( + File(p.join(d.sandbox, "test.css")) + .readAsBytesSync() + .sublist(0, bomBytes.length), + isNot(equals(bomBytes))); + }); + }); } diff --git a/test/dart_api_test.dart b/test/dart_api_test.dart index 75737617..630a333d 100644 --- a/test/dart_api_test.dart +++ b/test/dart_api_test.dart @@ -185,4 +185,44 @@ main() { expect(css, equals("a {\n b: from-importer;\n}")); }); }); + + group("charset", () { + group("= true", () { + test("doesn't emit @charset for a pure-ASCII stylesheet", () { + expect(compileString("a {b: c}"), equals(""" +a { + b: c; +}""")); + }); + + test("emits @charset with expanded output", () async { + expect(compileString("a {b: πŸ‘­}"), equals(""" +@charset "UTF-8"; +a { + b: πŸ‘­; +}""")); + }); + + test("emits a BOM with compressed output", () async { + expect(compileString("a {b: πŸ‘­}", style: OutputStyle.compressed), + equals("\u{FEFF}a{b:πŸ‘­}")); + }); + }); + + group("= false", () { + test("doesn't emit @charset with expanded output", () async { + expect(compileString("a {b: πŸ‘­}", charset: false), equals(""" +a { + b: πŸ‘­; +}""")); + }); + + test("emits a BOM with compressed output", () async { + expect( + compileString("a {b: πŸ‘­}", + charset: false, style: OutputStyle.compressed), + equals("a{b:πŸ‘­}")); + }); + }); + }); } From 413c949446233e0566b57ad3906fbcd0ec00d365 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 18 Apr 2019 16:34:48 -0700 Subject: [PATCH 10/27] Make FilesystemImport._loadPath absolute (#651) This avoids having the effective load path change when the process's working directory changes. --- CHANGELOG.md | 7 +++++++ lib/src/importer/filesystem.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f45686b0..b83fdaee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.18.1 + +### Dart API + +* `FilesystemImporter` now doesn't change its effective directory if the working + directory changes, even if it's passed a relative argument. + ## 1.18.0 * Avoid recursively listing directories when finding the canonical name of a diff --git a/lib/src/importer/filesystem.dart b/lib/src/importer/filesystem.dart index e403a629..3fe08fce 100644 --- a/lib/src/importer/filesystem.dart +++ b/lib/src/importer/filesystem.dart @@ -18,7 +18,7 @@ class FilesystemImporter extends Importer { final String _loadPath; /// Creates an importer that loads files relative to [loadPath]. - FilesystemImporter(this._loadPath); + FilesystemImporter(String loadPath) : _loadPath = p.absolute(loadPath); Uri canonicalize(Uri url) { if (url.scheme != 'file' && url.scheme != '') return null; diff --git a/pubspec.yaml b/pubspec.yaml index 13e319fc..0e76b5bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.18.0 +version: 1.18.1-dev description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass From 37f0740307043f28474fa062bd41125362ecbf63 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 18 Apr 2019 17:37:20 -0700 Subject: [PATCH 11/27] Allow ! in url()s (#650) Closes #646 --- CHANGELOG.md | 4 +++- lib/src/parse/stylesheet.dart | 3 ++- pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b83fdaee..ed27e95a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -## 1.18.1 +## 1.19.0 + +* Allow `!` in `url()`s without quotes. ### Dart API diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index ecc99408..e39f3526 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -2621,7 +2621,8 @@ relase. For details, see http://bit.ly/moz-document. var next = scanner.peekChar(); if (next == null) { break; - } else if (next == $percent || + } else if (next == $exclamation || + next == $percent || next == $ampersand || (next >= $asterisk && next <= $tilde) || next >= 0x0080) { diff --git a/pubspec.yaml b/pubspec.yaml index 0e76b5bb..e2fbf5f1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.18.1-dev +version: 1.19.0 description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass From 867674433ffdc668d43fcf932b498ef9ec50e2db Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 23 Apr 2019 11:41:16 -0700 Subject: [PATCH 12/27] Rotate Node.js releases that we test against --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b84d4e79..45a9c199 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,10 +18,10 @@ env: - TASK=tests DART_CHANNEL=dev - TASK=tests NODE_VERSION=stable - # Keep these up-to-date with the latest LTA Node releases. They next need to be - # rotated October 2018. See https://github.com/nodejs/Release. - - TASK=tests NODE_VERSION=lts/boron + # Keep these up-to-date with the latest LTS Node releases. They next need to + # be rotated December 2019. See https://github.com/nodejs/Release. - TASK=tests NODE_VERSION=lts/carbon + - TASK=tests NODE_VERSION=lts/dubnium # Miscellaneous checks. - TASK=analyze From c0f79c9fe20649f97967406989b73192484861be Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 23 Apr 2019 12:29:57 -0700 Subject: [PATCH 13/27] Use Node 11.x rather than 12.x This works around laverdet/node-fibers#409. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 45a9c199..ec92e287 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,8 @@ env: # Unit tests, defined in test/. - TASK=tests - TASK=tests DART_CHANNEL=dev - - TASK=tests NODE_VERSION=stable + # TODO(nweiz): Make this "stable" when laverdet/node-fibers#409 is fixed. + - TASK=tests NODE_VERSION=11 # Keep these up-to-date with the latest LTS Node releases. They next need to # be rotated December 2019. See https://github.com/nodejs/Release. From 7bd0f422b96948f089937c5db7c4ddceb0846657 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 23 Apr 2019 11:45:49 -0700 Subject: [PATCH 14/27] Test Windows and OS X on Travis --- .travis.yml | 93 +++++++++++++++++++++------------- appveyor.yml | 32 +++--------- test/cli/dart/update_test.dart | 3 +- test/cli/dart/watch_test.dart | 3 +- test/cli/node/update_test.dart | 3 +- test/cli/node/watch_test.dart | 3 +- tool/travis/test.sh | 2 +- 7 files changed, 74 insertions(+), 65 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec92e287..c01a0869 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,35 +1,12 @@ ## Testing # Set the language to Ruby so that we can run sass-spec tests. -language: ruby +language: shell env: global: - DART_CHANNEL=stable - DART_VERSION=latest - matrix: - # Language specs, defined in sass/sass-spec - - TASK=specs - - TASK=specs DART_CHANNEL=dev - - TASK=specs ASYNC=true - - # Unit tests, defined in test/. - - TASK=tests - - TASK=tests DART_CHANNEL=dev - # TODO(nweiz): Make this "stable" when laverdet/node-fibers#409 is fixed. - - TASK=tests NODE_VERSION=11 - - # Keep these up-to-date with the latest LTS Node releases. They next need to - # be rotated December 2019. See https://github.com/nodejs/Release. - - TASK=tests NODE_VERSION=lts/carbon - - TASK=tests NODE_VERSION=lts/dubnium - - # Miscellaneous checks. - - TASK=analyze - - TASK=format - -rvm: -- 2.3.1 # Only building master means that we don't run two builds for each pull request. branches: @@ -46,17 +23,17 @@ cache: install: # Install the Dart SDK. -- curl -o dart.zip "https://storage.googleapis.com/dart-archive/channels/$DART_CHANNEL/release/$DART_VERSION/sdk/dartsdk-linux-x64-release.zip" +- os="$TRAVIS_OS_NAME" +- if [ "$os" = osx ]; then os=macos; fi +- curl -o dart.zip "https://storage.googleapis.com/dart-archive/channels/$DART_CHANNEL/release/$DART_VERSION/sdk/dartsdk-$os-x64-release.zip" - unzip dart.zip -- export PATH="$PATH:`pwd`/dart-sdk/bin" +- export PATH="$PATH:`pwd`/dart-sdk/bin"; +- if [ "$os" = windows ]; then echo 'pub.bat "$@"' > `pwd`/dart-sdk/bin/pub; fi +- if [ "$os" = windows ]; then chmod a+x `pwd`/dart-sdk/bin/pub; fi - pub get -# Install the Node SDK if we're running Node tests. -- if-node() { if [ ! -z "$NODE_VERSION" ]; then "$@"; fi } -- if-node . "$HOME/.nvm/nvm.sh" -- if-node nvm install "$NODE_VERSION" -- if-node nvm use "$NODE_VERSION" -- if-node pub run grinder before-test +# Compile to JavaScript if we're running Node tests. +- if [ ! -z "$TRAVIS_NODE_VERSION" ]; then pub run grinder before-test; fi # Download sass-spec and install its dependencies if we're running specs. - if-specs() { if [ "$TASK" = specs ]; then "$@"; fi } @@ -70,10 +47,58 @@ install: script: tool/travis/test.sh -## Deployment - jobs: include: + ## Testing + + # Language specs, defined in sass/sass-spec. These need Ruby to run the spec + # runner. + - language: ruby + env: TASK=specs + - language: ruby + env: TASK=specs DART_CHANNEL=dev + - language: ruby + env: TASK=specs ASYNC=true + + # Unit tests, defined in test/. + - env: TASK=tests + - env: TASK=tests DART_CHANNEL=dev + - language: node_js + # TODO(nweiz): Make this "stable" when laverdet/node-fibers#409 is fixed. + node_js: 11 + env: TASK=tests + + # Keep these up-to-date with the latest LTS Node releases. They next need to + # be rotated December 2019. See https://github.com/nodejs/Release. + - language: node_js + node_js: lts/carbon + env: TASK=tests + - language: node_js + node_js: lts/dubnium + env: TASK=tests + + # Minimize tests on other OSes to avoid long cycle times. + - os: windows + # We have to explicitly list "shell" as the language because Windows doesn't + # support Ruby yet. + env: TASK=tests + # TODO(nweiz): Run Node tests on Windows when [this issue][] is fixed. + # + # [this issue]: https://travis-ci.community/t/windows-instances-hanging-before-install/250/28. + - os: osx + env: TASK=tests + - os: osx + language: node_js + # TODO(nweiz): Make this "stable" when laverdet/node-fibers#409 is fixed. + node_js: 11 + env: TASK=tests + + # Miscellaneous checks. + - env: TASK=analyze + - env: TASK=format + + ## Deployment + # Sanity check before releasing anywhere. - stage: sanity check if: &deploy-if diff --git a/appveyor.yml b/appveyor.yml index 14183ae8..5e30b503 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,34 +8,14 @@ branches: # Semantic version tags and legacy branches of the form "1.2.x". - "/^\\d+\\.\\d+\\.(\\d+([+-].*)?|x)$/" -# Don't run specs because sass-spec doesn't support Windows. They're also -# supposed to be platform-independent. -environment: - matrix: - - {TASK: tests, NODE: false} - - {TASK: tests, NODE: true} - install: - choco install dart-sdk - refreshenv - pub get -- ps: >- - If ($env:NODE -eq "true") { - Install-Product node '' - pub run grinder npm-package - } -- ps: >- - If ($env:NODE -eq "false") { - pub run grinder app-snapshot - } +- ps: Install-Product node '' +- pub run grinder npm-package +- npm install -# Run this as CMD because npm's stderr will cause PowerShell to think it failed. -- IF "%NODE%"=="true" npm install - -test_script: -- ps: >- - If ($env:NODE -eq "true") { - pub run test -t node - } Else { - pub run test -p vm -x node - } +# Only run Node tests on Windows because they won't work on Travis. See +# https://travis-ci.community/t/windows-instances-hanging-before-install/250/28. +test_script: pub run test -t node diff --git a/test/cli/dart/update_test.dart b/test/cli/dart/update_test.dart index 2c83e827..ef401f91 100644 --- a/test/cli/dart/update_test.dart +++ b/test/cli/dart/update_test.dart @@ -2,7 +2,8 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -@TestOn('vm') +// OS X's modification time reporting is flaky, so we skip these tests on it. +@TestOn('vm && !mac-os') import 'package:test/test.dart'; diff --git a/test/cli/dart/watch_test.dart b/test/cli/dart/watch_test.dart index aff5a4c6..3e6fd7ea 100644 --- a/test/cli/dart/watch_test.dart +++ b/test/cli/dart/watch_test.dart @@ -2,7 +2,8 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -@TestOn('vm') +// OS X's modification time reporting is flaky, so we skip these tests on it. +@TestOn('vm && !mac-os') import 'package:test/test.dart'; diff --git a/test/cli/node/update_test.dart b/test/cli/node/update_test.dart index 306340b4..614f25da 100644 --- a/test/cli/node/update_test.dart +++ b/test/cli/node/update_test.dart @@ -2,7 +2,8 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -@TestOn('vm') +// OS X's modification time reporting is flaky, so we skip these tests on it. +@TestOn('vm && !mac-os') @Tags(['node']) import 'package:test/test.dart'; diff --git a/test/cli/node/watch_test.dart b/test/cli/node/watch_test.dart index 6c221582..f1bffdaa 100644 --- a/test/cli/node/watch_test.dart +++ b/test/cli/node/watch_test.dart @@ -2,7 +2,8 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -@TestOn('vm') +// OS X's modification time reporting is flaky, so we skip these tests on it. +@TestOn('vm && !mac-os') @Tags(['node']) import 'package:test/test.dart'; diff --git a/tool/travis/test.sh b/tool/travis/test.sh index 766ea45d..134979c6 100755 --- a/tool/travis/test.sh +++ b/tool/travis/test.sh @@ -13,7 +13,7 @@ elif [ "$TASK" = format ]; then echo "${bold}Ensuring Dart code is formatted.$none" ./tool/assert-formatted.sh elif [ "$TASK" = tests ]; then - if [ -z "$NODE_VERSION" ]; then + if [ -z "$TRAVIS_NODE_VERSION" ]; then echo "${bold}Running Dart tests against $(dart --version &> /dev/stdout).$none" pub run test -p vm -x node else From 80b70b9dc2f869485c791c6d8daeb86057f8b75c Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 23 Apr 2019 14:48:15 -0700 Subject: [PATCH 15/27] Skip synchronize_test on Windows --- test/synchronize_test.dart | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/test/synchronize_test.dart b/test/synchronize_test.dart index 8684e817..fb486d3f 100644 --- a/test/synchronize_test.dart +++ b/test/synchronize_test.dart @@ -2,7 +2,9 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -@TestOn('vm') +// Windows sees different bytes than other OSes, possibly because of newline +// normalization issues. +@TestOn('vm && !windows') import 'dart:convert'; import 'dart:io'; @@ -12,17 +14,20 @@ import 'package:test/test.dart'; import '../tool/grind/synchronize.dart' as synchronize; -void main() { - test("synchronized files are up-to-date", () { - synchronize.sources.forEach((sourcePath, targetPath) { - var source = File(sourcePath).readAsStringSync(); - var target = File(targetPath).readAsStringSync(); +/// The pattern of a checksum in a generated file. +final _checksumPattern = RegExp(r"^// Checksum: (.*)$", multiLine: true); - var hash = sha1.convert(utf8.encode(source)); - if (!target.contains("Checksum: $hash")) { - fail("$targetPath is out-of-date.\n" - "Run pub run grinder to update it."); - } +void main() { + synchronize.sources.forEach((sourcePath, targetPath) { + test("synchronized file $targetPath is up-to-date", () { + var target = File(targetPath).readAsStringSync(); + var actualHash = _checksumPattern.firstMatch(target)[1]; + + var source = File(sourcePath).readAsBytesSync(); + var expectedHash = sha1.convert(source).toString(); + expect(actualHash, equals(expectedHash), + reason: "$targetPath is out-of-date.\n" + "Run pub run grinder to update it."); }); }); } From 8b03319c97f1c2edd6498b1d558c9b455c93221b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 25 Apr 2019 10:50:31 -0700 Subject: [PATCH 16/27] Run each type of test from its own script file --- .travis.yml | 110 ++++++++++++++++----------------- tool/travis/task/dart_tests.sh | 7 +++ tool/travis/task/node_tests.sh | 7 +++ tool/travis/task/specs.sh | 8 +++ tool/travis/test.sh | 29 --------- tool/travis/use_dart.sh | 24 +++++++ 6 files changed, 100 insertions(+), 85 deletions(-) create mode 100755 tool/travis/task/dart_tests.sh create mode 100755 tool/travis/task/node_tests.sh create mode 100755 tool/travis/task/specs.sh delete mode 100755 tool/travis/test.sh create mode 100755 tool/travis/use_dart.sh diff --git a/.travis.yml b/.travis.yml index c01a0869..6e1f9dc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,31 +21,10 @@ cache: directories: - $HOME/.pub-cache -install: # Install the Dart SDK. -- os="$TRAVIS_OS_NAME" -- if [ "$os" = osx ]; then os=macos; fi -- curl -o dart.zip "https://storage.googleapis.com/dart-archive/channels/$DART_CHANNEL/release/$DART_VERSION/sdk/dartsdk-$os-x64-release.zip" -- unzip dart.zip +before_install: +- tool/travis/use_dart.sh - export PATH="$PATH:`pwd`/dart-sdk/bin"; -- if [ "$os" = windows ]; then echo 'pub.bat "$@"' > `pwd`/dart-sdk/bin/pub; fi -- if [ "$os" = windows ]; then chmod a+x `pwd`/dart-sdk/bin/pub; fi -- pub get - -# Compile to JavaScript if we're running Node tests. -- if [ ! -z "$TRAVIS_NODE_VERSION" ]; then pub run grinder before-test; fi - -# Download sass-spec and install its dependencies if we're running specs. -- if-specs() { if [ "$TASK" = specs ]; then "$@"; fi } -- if-specs export sass_spec_ref=`tool/travis/sass-spec-ref.sh` -- if-specs git init sass-spec -- if-specs git -C sass-spec fetch git://github.com/sass/sass-spec "$sass_spec_ref" --depth 1 -- if-specs git -C sass-spec checkout FETCH_HEAD -- if-specs bundle install --gemfile=sass-spec/Gemfile --jobs=3 --retry=3 - -- if [ "$TASK" = tests ]; then pub run grinder app-snapshot; fi - -script: tool/travis/test.sh jobs: include: @@ -53,49 +32,68 @@ jobs: # Language specs, defined in sass/sass-spec. These need Ruby to run the spec # runner. - - language: ruby - env: TASK=specs - - language: ruby - env: TASK=specs DART_CHANNEL=dev - - language: ruby - env: TASK=specs ASYNC=true + - &specs + name: sass-spec | Dart stable | synchronous + language: ruby + install: + - export sass_spec_ref=`tool/travis/sass-spec-ref.sh` + - git init sass-spec + - git -C sass-spec fetch git://github.com/sass/sass-spec "$sass_spec_ref" --depth 1 + - git -C sass-spec checkout FETCH_HEAD + - bundle install --gemfile=sass-spec/Gemfile --jobs=3 --retry=3 + script: tool/travis/task/specs.sh + - <<: *specs + name: sass-spec | Dart dev | synchronous + env: DART_CHANNEL=dev + - <<: *specs + name: sass-spec | Dart stable | asynchronous + env: ASYNC=true - # Unit tests, defined in test/. - - env: TASK=tests - - env: TASK=tests DART_CHANNEL=dev - - language: node_js - # TODO(nweiz): Make this "stable" when laverdet/node-fibers#409 is fixed. - node_js: 11 - env: TASK=tests + # Pure Dart unit tests, defined in test/. + - &dart-tests + name: Dart tests | Dart stable + install: pub run grinder app-snapshot + task: tool/travis/task/dart_tests.sh + - <<: *dart-tests + name: Dart tests | Dart dev + env: DART_CHANNEL=dev + - <<: *dart-tests + os: windows + - <<: *dart-tests + os: osx - # Keep these up-to-date with the latest LTS Node releases. They next need to - # be rotated December 2019. See https://github.com/nodejs/Release. - - language: node_js - node_js: lts/carbon - env: TASK=tests - - language: node_js - node_js: lts/dubnium - env: TASK=tests - - # Minimize tests on other OSes to avoid long cycle times. - - os: windows - # We have to explicitly list "shell" as the language because Windows doesn't - # support Ruby yet. - env: TASK=tests + # Unit tests that use Node.js, defined in test/. + # + # The versions should be kept up-to-date with the latest LTS Node releases. + # They next need to be rotated December 2019. See + # https://github.com/nodejs/Release. + # # TODO(nweiz): Run Node tests on Windows when [this issue][] is fixed. # # [this issue]: https://travis-ci.community/t/windows-instances-hanging-before-install/250/28. - - os: osx - env: TASK=tests - - os: osx + - &node-tests + name: Node tests | Dart stable | Node 11 language: node_js # TODO(nweiz): Make this "stable" when laverdet/node-fibers#409 is fixed. node_js: 11 - env: TASK=tests + install: pub run grinder before-test + script: tool/travis/task/node_tests.sh + - <<: *node-tests + name: Node tests | Dart stable | Node Carbon + node_js: lts/carbon + - <<: *node-tests + name: Node tests | Dart stable | Node Dubnium + node_js: lts/dubnium + - <<: *node-tests + os: osx # Miscellaneous checks. - - env: TASK=analyze - - env: TASK=format + - name: static analysis + language: dart + dart_task: {dartanalyzer: --fatal-warnings lib tool test} + - name: code formatting + language: dart + dart_task: dartfmt ## Deployment diff --git a/tool/travis/task/dart_tests.sh b/tool/travis/task/dart_tests.sh new file mode 100755 index 00000000..949b2f5b --- /dev/null +++ b/tool/travis/task/dart_tests.sh @@ -0,0 +1,7 @@ +#!/bin/bash -e +# Copyright 2019 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. + +echo "$(tput bold)Running Dart tests against Dart $(dart --version &> /dev/stdout).$(tput sgr0)" +pub run test -p vm -x node diff --git a/tool/travis/task/node_tests.sh b/tool/travis/task/node_tests.sh new file mode 100755 index 00000000..40d6b477 --- /dev/null +++ b/tool/travis/task/node_tests.sh @@ -0,0 +1,7 @@ +#!/bin/bash -e +# Copyright 2019 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. + +echo "$(tput bold)Running Node tests against Node $(node --version).$(tput sgr0)" +pub run test -j 2 -t node diff --git a/tool/travis/task/specs.sh b/tool/travis/task/specs.sh new file mode 100755 index 00000000..c30c14d2 --- /dev/null +++ b/tool/travis/task/specs.sh @@ -0,0 +1,8 @@ +#!/bin/bash -e +# Copyright 2019 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. + +echo "$(tput bold)Running sass-spec against $(dart --version &> /dev/stdout).$(tput sgr0)" +if [ "$ASYNC" = true ]; then extra_args="--dart-args --async"; fi +(cd sass-spec; bundle exec sass-spec.rb --dart .. $extra_args) diff --git a/tool/travis/test.sh b/tool/travis/test.sh deleted file mode 100755 index 134979c6..00000000 --- a/tool/travis/test.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -e -# Copyright 2018 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. - -bold=$(tput bold) -none=$(tput sgr0) - -if [ "$TASK" = analyze ]; then - echo "${bold}Analzing Dart code.$none" - dartanalyzer --fatal-warnings lib/ test/ tool/ -elif [ "$TASK" = format ]; then - echo "${bold}Ensuring Dart code is formatted.$none" - ./tool/assert-formatted.sh -elif [ "$TASK" = tests ]; then - if [ -z "$TRAVIS_NODE_VERSION" ]; then - echo "${bold}Running Dart tests against $(dart --version &> /dev/stdout).$none" - pub run test -p vm -x node - else - echo "${bold}Running Node tests against Node $(node --version).$none" - pub run test -j 2 -t node - fi; -else - echo "${bold}Running sass-spec against $(dart --version &> /dev/stdout).$none" - if [ "$ASYNC" = true ]; then - extra_args="--dart-args --async" - fi; - (cd sass-spec; bundle exec sass-spec.rb --dart .. $extra_args) -fi diff --git a/tool/travis/use_dart.sh b/tool/travis/use_dart.sh new file mode 100755 index 00000000..0e9e86e5 --- /dev/null +++ b/tool/travis/use_dart.sh @@ -0,0 +1,24 @@ +#!/bin/bash -e +# Copyright 2019 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. + +# A script that installs Dart and runs "pub get" if the current worker isn't +# using Travis's Dart support. + +if [ ! -z "$TRAVIS_DART_VERSION" ]; then exit 0; fi + +echo "$(tput bold)Installing Dart $DART_CHANNEL/$DART_VERSION.$(tput sgr0)" + +source tool/travis/utils.sh + +os="$TRAVIS_OS_NAME" +if [ "$os" = osx ]; then os=macos; fi +travis_cmd curl -o dart.zip "https://storage.googleapis.com/dart-archive/channels/$DART_CHANNEL/release/$DART_VERSION/sdk/dartsdk-$os-x64-release.zip" +travis_cmd unzip dart.zip + +export PATH="$PATH:`pwd`/dart-sdk/bin"; +if [ "$os" = windows ]; then echo 'pub.bat "$@"' > `pwd`/dart-sdk/bin/pub; fi +if [ "$os" = windows ]; then chmod a+x `pwd`/dart-sdk/bin/pub; fi + +travis_cmd `pwd`/dart-sdk/bin/pub get From 407eefded1ea8c2cc7497bca44e94a3b10eba6b8 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 30 Apr 2019 11:31:18 -0700 Subject: [PATCH 17/27] Add support for attribute selector modifiers (#658) Closes #656 --- CHANGELOG.md | 4 ++++ lib/src/ast/selector/attribute.dart | 22 ++++++++++++++++++---- lib/src/parse/selector.dart | 7 ++++++- lib/src/visitor/serialize.dart | 4 ++++ pubspec.yaml | 2 +- test/compressed_test.dart | 10 ++++++++++ 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed27e95a..6f27d3ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.20.0 + +* Support attribute selector modifiers, such as the `i` in `[title="test" i]`. + ## 1.19.0 * Allow `!` in `url()`s without quotes. diff --git a/lib/src/ast/selector/attribute.dart b/lib/src/ast/selector/attribute.dart index 428ed6d5..ffa28af3 100644 --- a/lib/src/ast/selector/attribute.dart +++ b/lib/src/ast/selector/attribute.dart @@ -27,15 +27,27 @@ class AttributeSelector extends SimpleSelector { /// regardless of this value. It's `null` if and only if [op] is `null`. final String value; + /// The modifier which indicates how the attribute selector should be + /// processed. + /// + /// See for example [case-sensitivity][] modifiers. + /// + /// [case-sensitivity]: https://www.w3.org/TR/selectors-4/#attribute-case + /// + /// If [op] is `null`, this is always `null` as well. + final String modifier; + /// Creates an attribute selector that matches any element with a property of /// the given name. AttributeSelector(this.name) : op = null, - value = null; + value = null, + modifier = null; /// Creates an attribute selector that matches an element with a property /// named [name], whose value matches [value] based on the semantics of [op]. - AttributeSelector.withOperator(this.name, this.op, this.value); + AttributeSelector.withOperator(this.name, this.op, this.value, + {this.modifier}); T accept(SelectorVisitor visitor) => visitor.visitAttributeSelector(this); @@ -44,9 +56,11 @@ class AttributeSelector extends SimpleSelector { other is AttributeSelector && other.name == name && other.op == op && - other.value == value; + other.value == value && + other.modifier == modifier; - int get hashCode => name.hashCode ^ op.hashCode ^ value.hashCode; + int get hashCode => + name.hashCode ^ op.hashCode ^ value.hashCode ^ modifier.hashCode; } /// An operator that defines the semantics of an [AttributeSelector]. diff --git a/lib/src/parse/selector.dart b/lib/src/parse/selector.dart index 6679ba96..2a80bf88 100644 --- a/lib/src/parse/selector.dart +++ b/lib/src/parse/selector.dart @@ -201,8 +201,13 @@ class SelectorParser extends Parser { : identifier(); whitespace(); + var modifier = isAlphabetic(scanner.peekChar()) + ? String.fromCharCode(scanner.readChar()) + : null; + scanner.expectChar($rbracket); - return AttributeSelector.withOperator(name, operator, value); + return AttributeSelector.withOperator(name, operator, value, + modifier: modifier); } /// Consumes a qualified name as part of an attribute selector. diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index 9ee003c2..edd6eba9 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -914,9 +914,13 @@ class _SerializeVisitor implements CssVisitor, ValueVisitor, SelectorVisitor { // doesn't consider them to be valid identifiers. !attribute.value.startsWith('--')) { _buffer.write(attribute.value); + + if (attribute.modifier != null) _buffer.writeCharCode($space); } else { _visitQuotedString(attribute.value); + if (attribute.modifier != null) _writeOptionalSpace(); } + if (attribute.modifier != null) _buffer.write(attribute.modifier); } _buffer.writeCharCode($rbracket); } diff --git a/pubspec.yaml b/pubspec.yaml index e2fbf5f1..c5aa4799 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.19.0 +version: 1.20.0-dev description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass diff --git a/test/compressed_test.dart b/test/compressed_test.dart index 210ddbad..27c73ea5 100644 --- a/test/compressed_test.dart +++ b/test/compressed_test.dart @@ -42,6 +42,16 @@ void main() { equals("a:nth-child(2n of b,c){x:y}")); }); }); + + group("in attribute selectors with modifiers", () { + test("removes whitespace when quotes are required", () { + expect(_compile('[a=" " b] {x: y}'), equals('[a=" "b]{x:y}')); + }); + + test("doesn't remove whitespace when quotes aren't required", () { + expect(_compile('[a="b"c] {x: y}'), equals('[a=b c]{x:y}')); + }); + }); }); group("for declarations", () { From 2ca00e88285b6a8a73e605a9eff4a8f514ca1663 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 30 Apr 2019 13:53:35 -0700 Subject: [PATCH 18/27] Re-enable testing against Node stable (#661) laverdet/node-fibers#409 has been fixed in Fibers 4.0.0. --- .travis.yml | 5 ++--- package.json | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6e1f9dc0..5b8fefe9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,10 +72,9 @@ jobs: # # [this issue]: https://travis-ci.community/t/windows-instances-hanging-before-install/250/28. - &node-tests - name: Node tests | Dart stable | Node 11 + name: Node tests | Dart stable | Node stable language: node_js - # TODO(nweiz): Make this "stable" when laverdet/node-fibers#409 is fixed. - node_js: 11 + node_js: stable install: pub run grinder before-test script: tool/travis/task/node_tests.sh - <<: *node-tests diff --git a/package.json b/package.json index 4732234f..aaf5ec81 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "devDependencies": { "node-sass": "^4.11.0", "chokidar": "^2.0.0", - "fibers": ">=1.0.0 <4.0.0", + "fibers": ">=1.0.0 <5.0.0", "intercept-stdout": "^0.1.2" } } From 892f301d5ade78adfe11dbdd8ce7bf53858353e8 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 2 May 2019 13:07:57 -0700 Subject: [PATCH 19/27] Remove a debugging print (#655) This never actually triggered because we always overrode the listener before the directory was modified. --- lib/src/io/vm.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/io/vm.dart b/lib/src/io/vm.dart index e439609c..c1399167 100644 --- a/lib/src/io/vm.dart +++ b/lib/src/io/vm.dart @@ -94,7 +94,7 @@ Future> watchDir(String path, {bool poll = false}) async { // triggers but the caller can still listen at their leisure. var stream = SubscriptionStream(watcher.events .transform(const SingleSubscriptionTransformer()) - .listen((e) => print(e))); + .listen(null)); await watcher.ready; return stream; From 75ec9241f075b7b3aa383b5255577a5b888604c2 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 2 May 2019 15:08:46 -0700 Subject: [PATCH 20/27] Build standalone Mac OS releases on Mac OS bots (#662) This will allow us to distribute application snapshots, rather than script snapshots, for Mac OS. --- .travis.yml | 14 +++- tool/grind/github.dart | 82 ++++++++++++--------- tool/grind/standalone.dart | 141 +++++++++++++++++++------------------ 3 files changed, 134 insertions(+), 103 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5b8fefe9..df9f1f11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -102,7 +102,8 @@ jobs: (type IN (push, api)) AND (repo = sass/dart-sass) AND tag =~ ^\d+\.\d+\.\d+([+-].*)?$ script: pub run grinder sanity-check-before-release - # Deploy to GitHub. + # Deploy Linux and Windows releases to GitHub. Mac OS releases are deployed in + # a later stage so that we can build application snapshots on Mac OS bots. - stage: deploy 1 if: *deploy-if env: &github-env @@ -115,7 +116,7 @@ jobs: script: skip # Don't run tests deploy: provider: script - script: pub run grinder github-release + script: pub run grinder github-release github-linux github-windows skip_cleanup: true # Don't clean up the Dart SDK. # This causes the deploy to only be build when a tag is pushed. This @@ -195,3 +196,12 @@ jobs: script: pub run grinder update-bazel skip_cleanup: true on: {tags: true} + + - if: *deploy-if + env: *github-env + script: skip + deploy: + provider: script + script: pub run grinder github-mac-os + skip_cleanup: true + on: {tags: true} diff --git a/tool/grind/github.dart b/tool/grind/github.dart index 22c16bf8..9127d7e5 100644 --- a/tool/grind/github.dart +++ b/tool/grind/github.dart @@ -13,16 +13,14 @@ import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; import 'package:string_scanner/string_scanner.dart'; -import '../grind.dart'; +import 'standalone.dart'; import 'utils.dart'; -@Task('Release the current version as to GitHub.') -@Depends(package) +@Task('Create a GitHub release for the current version, without executables.') githubRelease() async { var authorization = _githubAuthorization(); - var client = http.Client(); - var response = await client.post( + var response = await http.post( "https://api.github.com/repos/sass/dart-sass/releases", headers: { "content-type": "application/json", @@ -40,34 +38,6 @@ githubRelease() async { } else { log("Released Dart Sass $version to GitHub."); } - - var uploadUrl = json - .decode(response.body)["upload_url"] - // Remove the URL template. - .replaceFirst(RegExp(r"\{[^}]+\}$"), ""); - - await Future.wait(["linux", "macos", "windows"].expand((os) { - return ["ia32", "x64"].map((architecture) async { - var format = os == "windows" ? "zip" : "tar.gz"; - var package = "dart-sass-$version-$os-$architecture.$format"; - var response = await http.post("$uploadUrl?name=$package", - headers: { - "content-type": - os == "windows" ? "application/zip" : "application/gzip", - "authorization": authorization - }, - body: File(p.join("build", package)).readAsBytesSync()); - - if (response.statusCode != 201) { - fail("${response.statusCode} error uploading $package:\n" - "${response.body}"); - } else { - log("Uploaded $package."); - } - }); - })); - - client.close(); } /// Returns the Markdown-formatted message to use for a GitHub release. @@ -127,6 +97,52 @@ String _lastChangelogSection() { return buffer.toString().trim(); } +@Task('Release Linux executables to GitHub.') +@Depends(packageLinux) +githubLinux() => _uploadExecutables("linux"); + +@Task('Release Mac OS executables to GitHub.') +@Depends(packageMacOs) +githubMacOs() => _uploadExecutables("macos"); + +@Task('Release Windows executables to GitHub.') +@Depends(packageWindows) +githubWindows() => _uploadExecutables("windows"); + +/// Upload the 32- and 64-bit executables to the current GitHub release +Future _uploadExecutables(String os) async { + var authorization = _githubAuthorization(); + var client = http.Client(); + var response = await client.get( + "https://api.github.com/repos/sass/dart-sass/releases/tags/$version", + headers: {"authorization": authorization}); + + var uploadUrl = json + .decode(response.body)["upload_url"] + // Remove the URL template. + .replaceFirst(RegExp(r"\{[^}]+\}$"), ""); + + await Future.wait(["ia32", "x64"].map((architecture) async { + var format = os == "windows" ? "zip" : "tar.gz"; + var package = "dart-sass-$version-$os-$architecture.$format"; + var response = await http.post("$uploadUrl?name=$package", + headers: { + "content-type": + os == "windows" ? "application/zip" : "application/gzip", + "authorization": authorization + }, + body: File(p.join("build", package)).readAsBytesSync()); + + if (response.statusCode != 201) { + fail("${response.statusCode} error uploading $package:\n" + "${response.body}"); + } else { + log("Uploaded $package."); + } + })); + await client.close(); +} + /// Returns the HTTP basic authentication Authorization header from the /// environment. String _githubAuthorization() { diff --git a/tool/grind/standalone.dart b/tool/grind/standalone.dart index 98fffb19..09ef48e0 100644 --- a/tool/grind/standalone.dart +++ b/tool/grind/standalone.dart @@ -47,74 +47,79 @@ void _appSnapshot({@required bool release}) { arguments: ['tool/app-snapshot-input.scss'], vmArgs: args, quiet: true); } -@Task('Build standalone packages for all OSes.') +@Task('Build standalone packages for Linux.') @Depends(snapshot, releaseAppSnapshot) -package() async { +packageLinux() => _buildPackage("linux"); + +@Task('Build standalone packages for Mac OS.') +@Depends(snapshot, releaseAppSnapshot) +packageMacOs() => _buildPackage("macos"); + +@Task('Build standalone packages for Windows.') +@Depends(snapshot, releaseAppSnapshot) +packageWindows() => _buildPackage("windows"); + +/// Builds standalone 32- and 64-bit Sass packages for the given [os]. +Future _buildPackage(String os) async { var client = http.Client(); - await Future.wait(["linux", "macos", "windows"].expand((os) => [ - _buildPackage(client, os, x64: true), - _buildPackage(client, os, x64: false) - ])); - client.close(); -} - -/// Builds a standalone Sass package for the given [os] and architecture. -/// -/// The [client] is used to download the corresponding Dart SDK. -Future _buildPackage(http.Client client, String os, {bool x64 = true}) async { - var architecture = x64 ? "x64" : "ia32"; - - // TODO: Compile a single executable that embeds the Dart VM and the snapshot - // when dart-lang/sdk#27596 is fixed. - var channel = isDevSdk ? "dev" : "stable"; - var url = "https://storage.googleapis.com/dart-archive/channels/$channel/" - "release/$dartVersion/sdk/dartsdk-$os-$architecture-release.zip"; - log("Downloading $url..."); - var response = await client.get(Uri.parse(url)); - if (response.statusCode ~/ 100 != 2) { - throw "Failed to download package: ${response.statusCode} " - "${response.reasonPhrase}."; - } - - var dartExecutable = ZipDecoder().decodeBytes(response.bodyBytes).firstWhere( - (file) => os == 'windows' - ? file.name.endsWith("/bin/dart.exe") - : file.name.endsWith("/bin/dart")); - var executable = dartExecutable.content as List; - - // Use the app snapshot when packaging for the current operating system. - // - // TODO: Use an app snapshot everywhere when dart-lang/sdk#28617 is fixed. - var snapshot = os == Platform.operatingSystem && x64 == _is64Bit - ? "build/sass.dart.app.snapshot" - : "build/sass.dart.snapshot"; - - var archive = Archive() - ..addFile(fileFromBytes( - "dart-sass/src/dart${os == 'windows' ? '.exe' : ''}", executable, - executable: true)) - ..addFile( - file("dart-sass/src/DART_LICENSE", p.join(sdkDir.path, 'LICENSE'))) - ..addFile(file("dart-sass/src/sass.dart.snapshot", snapshot)) - ..addFile(file("dart-sass/src/SASS_LICENSE", "LICENSE")) - ..addFile(fileFromString( - "dart-sass/dart-sass${os == 'windows' ? '.bat' : ''}", - readAndReplaceVersion( - "package/dart-sass.${os == 'windows' ? 'bat' : 'sh'}"), - executable: true)) - ..addFile(fileFromString("dart-sass/sass${os == 'windows' ? '.bat' : ''}", - readAndReplaceVersion("package/sass.${os == 'windows' ? 'bat' : 'sh'}"), - executable: true)); - - var prefix = 'build/dart-sass-$version-$os-$architecture'; - if (os == 'windows') { - var output = "$prefix.zip"; - log("Creating $output..."); - File(output).writeAsBytesSync(ZipEncoder().encode(archive)); - } else { - var output = "$prefix.tar.gz"; - log("Creating $output..."); - File(output) - .writeAsBytesSync(GZipEncoder().encode(TarEncoder().encode(archive))); - } + await Future.wait(["ia32", "x64"].map((architecture) async { + // TODO: Compile a single executable that embeds the Dart VM and the + // snapshot when dart-lang/sdk#27596 is fixed. + var channel = isDevSdk ? "dev" : "stable"; + var url = "https://storage.googleapis.com/dart-archive/channels/$channel/" + "release/$dartVersion/sdk/dartsdk-$os-$architecture-release.zip"; + log("Downloading $url..."); + var response = await client.get(Uri.parse(url)); + if (response.statusCode ~/ 100 != 2) { + throw "Failed to download package: ${response.statusCode} " + "${response.reasonPhrase}."; + } + + var dartExecutable = ZipDecoder() + .decodeBytes(response.bodyBytes) + .firstWhere((file) => os == 'windows' + ? file.name.endsWith("/bin/dart.exe") + : file.name.endsWith("/bin/dart")); + var executable = dartExecutable.content as List; + + // Use the app snapshot when packaging for the current operating system. + // + // TODO: Use an app snapshot everywhere when dart-lang/sdk#28617 is fixed. + var snapshot = + os == Platform.operatingSystem && (architecture == "x64") == _is64Bit + ? "build/sass.dart.app.snapshot" + : "build/sass.dart.snapshot"; + + var archive = Archive() + ..addFile(fileFromBytes( + "dart-sass/src/dart${os == 'windows' ? '.exe' : ''}", executable, + executable: true)) + ..addFile( + file("dart-sass/src/DART_LICENSE", p.join(sdkDir.path, 'LICENSE'))) + ..addFile(file("dart-sass/src/sass.dart.snapshot", snapshot)) + ..addFile(file("dart-sass/src/SASS_LICENSE", "LICENSE")) + ..addFile(fileFromString( + "dart-sass/dart-sass${os == 'windows' ? '.bat' : ''}", + readAndReplaceVersion( + "package/dart-sass.${os == 'windows' ? 'bat' : 'sh'}"), + executable: true)) + ..addFile(fileFromString( + "dart-sass/sass${os == 'windows' ? '.bat' : ''}", + readAndReplaceVersion( + "package/sass.${os == 'windows' ? 'bat' : 'sh'}"), + executable: true)); + + var prefix = 'build/dart-sass-$version-$os-$architecture'; + if (os == 'windows') { + var output = "$prefix.zip"; + log("Creating $output..."); + File(output).writeAsBytesSync(ZipEncoder().encode(archive)); + } else { + var output = "$prefix.tar.gz"; + log("Creating $output..."); + File(output) + .writeAsBytesSync(GZipEncoder().encode(TarEncoder().encode(archive))); + } + })); + await client.close(); } From 57260a9fc153cedfd8ebc380c0a93f2f82c04112 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 2 May 2019 16:16:33 -0700 Subject: [PATCH 21/27] Update color algorithm URLs (#664) --- lib/src/value/color.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/value/color.dart b/lib/src/value/color.dart index c0fb7e2b..b4aca7a3 100644 --- a/lib/src/value/color.dart +++ b/lib/src/value/color.dart @@ -133,7 +133,7 @@ class SassColor extends Value implements ext.SassColor { /// Computes [_hue], [_saturation], and [_value] based on [red], [green], and /// [blue]. void _rgbToHsl() { - // Algorithm from http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + // Algorithm from https://en.wikipedia.org/wiki/HSL_and_HSV#RGB_to_HSL_and_HSV var scaledRed = red / 255; var scaledGreen = green / 255; var scaledBlue = blue / 255; @@ -166,7 +166,7 @@ class SassColor extends Value implements ext.SassColor { /// Computes [_red], [_green], and [_blue] based on [hue], [saturation], and /// [value]. void _hslToRgb() { - // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. + // Algorithm from the CSS3 spec: https://www.w3.org/TR/css3-color/#hsl-color. var scaledHue = hue / 360; var scaledSaturation = saturation / 100; var scaledLightness = lightness / 100; From 34c068a18951dc14130e9924d111c0d4bc03edf6 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 2 May 2019 18:14:25 -0700 Subject: [PATCH 22/27] Generate a CSS file describing an error when one occurs (#659) Closes #657 --- CHANGELOG.md | 13 ++++ lib/src/exception.dart | 45 +++++++++++ lib/src/executable.dart | 4 +- lib/src/executable/compile_stylesheet.dart | 87 ++++++++++++--------- lib/src/executable/options.dart | 9 +++ lib/src/executable/watch.dart | 2 +- pubspec.yaml | 2 +- test/cli/shared.dart | 47 +++++++++++ test/cli/shared/colon_args.dart | 15 ++-- test/cli/shared/update.dart | 54 ++++++++++--- test/cli/shared/watch.dart | 90 ++++++++++++++-------- 11 files changed, 280 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f27d3ab..3dbd3943 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ * Support attribute selector modifiers, such as the `i` in `[title="test" i]`. +### Command-Line Interface + +* When compilation fails, Sass will now write the error message to the CSS + output as a comment and as the `content` property of a `body::before` rule so + it will show up in the browser (unless compiling to standard output). This can + be disabled with the `--no-error-css` flag, or forced even when compiling to + standard output with the `--error-css` flag. + +### Dart API + +* Added `SassException.toCssString()`, which returns the contents of a CSS + stylesheet describing the error, as above. + ## 1.19.0 * Allow `!` in `url()`s without quotes. diff --git a/lib/src/exception.dart b/lib/src/exception.dart index 50e65e2d..033fbab0 100644 --- a/lib/src/exception.dart +++ b/lib/src/exception.dart @@ -2,10 +2,13 @@ // 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:stack_trace/stack_trace.dart'; +import 'package:term_glyph/term_glyph.dart' as term_glyph; import 'utils.dart'; +import 'value.dart'; /// An exception thrown by Sass. class SassException extends SourceSpanException { @@ -30,6 +33,48 @@ class SassException extends SourceSpanException { } return buffer.toString(); } + + /// Returns the contents of a CSS stylesheet that will display this error + /// message above the current page. + String toCssString() { + // Don't render the error message in Unicode for the inline comment, since + // we can't be sure the user's default encoding is UTF-8. + var wasAscii = term_glyph.ascii; + term_glyph.ascii = true; + // Replace comment-closing sequences in the error message with + // visually-similar sequences that won't actually close the comment. + var commentMessage = toString(color: false).replaceAll("*/", "*βˆ•"); + term_glyph.ascii = wasAscii; + + // For the string comment, render all non-ASCII characters as escape + // sequences so that they'll show up even if the HTTP headers are set + // incorrectly. + var stringMessage = StringBuffer(); + for (var rune in SassString(toString(color: false)).toString().runes) { + if (rune > 0xFF) { + stringMessage + ..writeCharCode($backslash) + ..write(rune.toRadixString(16)) + ..writeCharCode($space); + } else { + stringMessage.writeCharCode(rune); + } + } + + return """ +/* ${commentMessage.split("\n").join("\n * ")} */ + +body::before { + font-family: "Source Code Pro", "SF Mono", Monaco, Inconsolata, "Fira Mono", + "Droid Sans Mono", monospace, monospace; + white-space: pre; + display: block; + padding: 1em; + margin-bottom: 1em; + border-bottom: 2px solid black; + content: $stringMessage; +}"""; + } } /// An exception thrown by Sass while evaluating a stylesheet. diff --git a/lib/src/executable.dart b/lib/src/executable.dart index 341bb143..a573ce58 100644 --- a/lib/src/executable.dart +++ b/lib/src/executable.dart @@ -69,7 +69,9 @@ main(List args) async { // dart-lang/sdk#33400. () { try { - if (destination != null) deleteFile(destination); + if (destination != null && !options.emitErrorCss) { + deleteFile(destination); + } } on FileSystemException { // If the file doesn't exist, that's fine. } diff --git a/lib/src/executable/compile_stylesheet.dart b/lib/src/executable/compile_stylesheet.dart index 4db0c08d..fa93112a 100644 --- a/lib/src/executable/compile_stylesheet.dart +++ b/lib/src/executable/compile_stylesheet.dart @@ -10,12 +10,13 @@ import 'package:source_maps/source_maps.dart'; import '../async_import_cache.dart'; import '../compile.dart'; -import '../visitor/serialize.dart'; +import '../exception.dart'; import '../importer/filesystem.dart'; import '../io.dart'; import '../stylesheet_graph.dart'; import '../syntax.dart'; import '../utils.dart'; +import '../visitor/serialize.dart'; import 'options.dart'; /// Compiles the stylesheet at [source] to [destination]. @@ -56,43 +57,55 @@ Future compileStylesheet(ExecutableOptions options, StylesheetGraph graph, } CompileResult result; - if (options.asynchronous) { - var importCache = AsyncImportCache([], - loadPaths: options.loadPaths, logger: options.logger); + try { + if (options.asynchronous) { + var importCache = AsyncImportCache([], + loadPaths: options.loadPaths, logger: options.logger); - result = source == null - ? await compileStringAsync(await readStdin(), - syntax: syntax, - logger: options.logger, - importCache: importCache, - importer: FilesystemImporter('.'), - style: options.style, - sourceMap: options.emitSourceMap, - charset: options.charset) - : await compileAsync(source, - syntax: syntax, - logger: options.logger, - importCache: importCache, - style: options.style, - sourceMap: options.emitSourceMap, - charset: options.charset); - } else { - result = source == null - ? compileString(await readStdin(), - syntax: syntax, - logger: options.logger, - importCache: graph.importCache, - importer: FilesystemImporter('.'), - style: options.style, - sourceMap: options.emitSourceMap, - charset: options.charset) - : compile(source, - syntax: syntax, - logger: options.logger, - importCache: graph.importCache, - style: options.style, - sourceMap: options.emitSourceMap, - charset: options.charset); + result = source == null + ? await compileStringAsync(await readStdin(), + syntax: syntax, + logger: options.logger, + importCache: importCache, + importer: FilesystemImporter('.'), + style: options.style, + sourceMap: options.emitSourceMap, + charset: options.charset) + : await compileAsync(source, + syntax: syntax, + logger: options.logger, + importCache: importCache, + style: options.style, + sourceMap: options.emitSourceMap, + charset: options.charset); + } else { + result = source == null + ? compileString(await readStdin(), + syntax: syntax, + logger: options.logger, + importCache: graph.importCache, + importer: FilesystemImporter('.'), + style: options.style, + sourceMap: options.emitSourceMap, + charset: options.charset) + : compile(source, + syntax: syntax, + logger: options.logger, + importCache: graph.importCache, + style: options.style, + sourceMap: options.emitSourceMap, + charset: options.charset); + } + } on SassException catch (error) { + if (options.emitErrorCss) { + if (destination == null) { + print(error.toCssString()); + } else { + ensureDir(p.dirname(destination)); + writeFile(destination, error.toCssString() + "\n"); + } + } + rethrow; } var css = result.css; diff --git a/lib/src/executable/options.dart b/lib/src/executable/options.dart index c8363c8c..381d48fe 100644 --- a/lib/src/executable/options.dart +++ b/lib/src/executable/options.dart @@ -58,6 +58,10 @@ class ExecutableOptions { ..addFlag('charset', help: 'Emit a @charset or BOM for CSS with non-ASCII characters.', defaultsTo: true) + ..addFlag('error-css', + help: 'When an error occurs, emit a stylesheet describing it.\n' + 'Defaults to true when compiling to a file.', + defaultsTo: null) ..addFlag('update', help: 'Only compile out-of-date stylesheets.', negatable: false); @@ -200,6 +204,11 @@ class ExecutableOptions { /// error. bool get stopOnError => _options['stop-on-error'] as bool; + /// Whether to emit error messages as CSS stylesheets + bool get emitErrorCss => + _options['error-css'] as bool ?? + sourcesToDestinations.values.any((destination) => destination != null); + /// A map from source paths to the destination paths where the compiled CSS /// should be written. /// diff --git a/lib/src/executable/watch.dart b/lib/src/executable/watch.dart index 9b75f9cb..683cdcbe 100644 --- a/lib/src/executable/watch.dart +++ b/lib/src/executable/watch.dart @@ -74,7 +74,7 @@ class _Watcher { ifModified: ifModified); return true; } on SassException catch (error, stackTrace) { - _delete(destination); + if (!_options.emitErrorCss) _delete(destination); _printError(error.toString(color: _options.color), stackTrace); exitCode = 65; return false; diff --git a/pubspec.yaml b/pubspec.yaml index c5aa4799..cb42f299 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.20.0-dev +version: 1.20.0 description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass diff --git a/test/cli/shared.dart b/test/cli/shared.dart index d909c33c..db4e8474 100644 --- a/test/cli/shared.dart +++ b/test/cli/shared.dart @@ -476,4 +476,51 @@ void sharedTests( isNot(equals(bomBytes))); }); }); + + group("with --error-css", () { + var message = "Error: Expected expression."; + setUp(() => d.file("test.scss", "a {b: 1 + }").create()); + + group("not explicitly set", () { + test("doesn't emit error CSS when compiling to stdout", () async { + var sass = await runSass(["test.scss"]); + expect(sass.stdout, emitsDone); + await sass.shouldExit(65); + }); + + test("emits error CSS when compiling to a file", () async { + var sass = await runSass(["test.scss", "test.css"]); + await sass.shouldExit(65); + await d.file("test.css", contains(message)).validate(); + }); + }); + + group("explicitly set", () { + test("emits error CSS when compiling to stdout", () async { + var sass = await runSass(["--error-css", "test.scss"]); + expect(sass.stdout, emitsThrough(contains(message))); + await sass.shouldExit(65); + }); + + test("emits error CSS when compiling to a file", () async { + var sass = await runSass(["--error-css", "test.scss", "test.css"]); + await sass.shouldExit(65); + await d.file("test.css", contains(message)).validate(); + }); + }); + + group("explicitly unset", () { + test("doesn't emit error CSS when compiling to stdout", () async { + var sass = await runSass(["--no-error-css", "test.scss"]); + expect(sass.stdout, emitsDone); + await sass.shouldExit(65); + }); + + test("emits error CSS when compiling to a file", () async { + var sass = await runSass(["--no-error-css", "test.scss", "test.css"]); + await sass.shouldExit(65); + await d.nothing("test.css").validate(); + }); + }); + }); } diff --git a/test/cli/shared/colon_args.dart b/test/cli/shared/colon_args.dart index 806ae515..f1296ee7 100644 --- a/test/cli/shared/colon_args.dart +++ b/test/cli/shared/colon_args.dart @@ -72,13 +72,14 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test1.scss", "a {b: }").create(); await d.file("test2.scss", "x {y: z}").create(); + var message = 'Error: Expected expression.'; var sass = await runSass( ["--no-source-map", "test1.scss:out1.css", "test2.scss:out2.css"]); - await expectLater(sass.stderr, emits('Error: Expected expression.')); + await expectLater(sass.stderr, emits(message)); await expectLater(sass.stderr, emitsThrough(contains('test1.scss 1:7'))); await sass.shouldExit(65); - await d.nothing("out1.css").validate(); + await d.file("out1.css", contains(message)).validate(); await d .file("out2.css", equalsIgnoringWhitespace("x { y: z; }")) .validate(); @@ -88,18 +89,16 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test1.scss", "a {b: }").create(); await d.file("test2.scss", "x {y: z}").create(); + var message = 'Error: Expected expression.'; var sass = await runSass( ["--stop-on-error", "test1.scss:out1.css", "test2.scss:out2.css"]); await expectLater( sass.stderr, - emitsInOrder([ - 'Error: Expected expression.', - emitsThrough(contains('test1.scss 1:7')), - emitsDone - ])); + emitsInOrder( + [message, emitsThrough(contains('test1.scss 1:7')), emitsDone])); await sass.shouldExit(65); - await d.nothing("out1.css").validate(); + await d.file("out1.css", contains(message)).validate(); await d.nothing("out2.css").validate(); }); diff --git a/test/cli/shared/update.dart b/test/cli/shared/update.dart index 40e568ac..fa513213 100644 --- a/test/cli/shared/update.dart +++ b/test/cli/shared/update.dart @@ -153,12 +153,13 @@ void sharedTests(Future runSass(Iterable arguments)) { test("with a missing import", () async { await d.file("test.scss", "@import 'other'").create(); + var message = "Error: Can't find stylesheet to import."; var sass = await update(["test.scss:out.css"]); - expect(sass.stderr, emits("Error: Can't find stylesheet to import.")); + expect(sass.stderr, emits(message)); expect(sass.stderr, emitsThrough(contains("test.scss 1:9"))); await sass.shouldExit(65); - await d.nothing("out.css").validate(); + await d.file("out.css", contains(message)).validate(); }); test("with a conflicting import", () async { @@ -166,29 +167,46 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("other.scss", "a {b: c}").create(); await d.file("_other.scss", "x {y: z}").create(); + var message = "Error: It's not clear which file to import. Found:"; var sass = await update(["test.scss:out.css"]); - expect(sass.stderr, - emits("Error: It's not clear which file to import. Found:")); + expect(sass.stderr, emits(message)); expect(sass.stderr, emitsThrough(contains("test.scss 1:9"))); await sass.shouldExit(65); - await d.nothing("out.css").validate(); + await d.file("out.css", contains(message)).validate(); }); }); - group("removes a CSS file", () { + group("updates a CSS file", () { test("when a file has an error", () async { await d.file("test.scss", "a {b: c}").create(); await (await update(["test.scss:out.css"])).shouldExit(0); await d.file("out.css", anything).validate(); + var message = "Error: Expected expression."; await d.file("test.scss", "a {b: }").create(); var sass = await update(["test.scss:out.css"]); - expect(sass.stderr, emits("Error: Expected expression.")); + expect(sass.stderr, emits(message)); expect(sass.stderr, emitsThrough(contains("test.scss 1:7"))); await sass.shouldExit(65); - await d.nothing("out.css").validate(); + await d.file("out.css", contains(message)).validate(); + }); + + test("when a file has an error even with another stdout output", () async { + await d.file("test.scss", "a {b: c}").create(); + await (await update(["test.scss:out.css"])).shouldExit(0); + await d.file("out.css", anything).validate(); + + var message = "Error: Expected expression."; + await d.file("test.scss", "a {b: }").create(); + await d.file("other.scss", "x {y: z}").create(); + var sass = await update(["test.scss:out.css", "other.scss:-"]); + expect(sass.stderr, emits(message)); + expect(sass.stderr, emitsThrough(contains("test.scss 1:7"))); + await sass.shouldExit(65); + + await d.file("out.css", contains(message)).validate(); }); test("when an import is removed", () async { @@ -197,16 +215,32 @@ void sharedTests(Future runSass(Iterable arguments)) { await (await update(["test.scss:out.css"])).shouldExit(0); await d.file("out.css", anything).validate(); + var message = "Error: Can't find stylesheet to import."; d.file("_other.scss").io.deleteSync(); var sass = await update(["test.scss:out.css"]); - expect(sass.stderr, emits("Error: Can't find stylesheet to import.")); + expect(sass.stderr, emits(message)); expect(sass.stderr, emitsThrough(contains("test.scss 1:9"))); await sass.shouldExit(65); - await d.nothing("out.css").validate(); + await d.file("out.css", contains(message)).validate(); }); }); + test("deletes a CSS file when a file has an error with --no-error-css", + () async { + await d.file("test.scss", "a {b: c}").create(); + await (await update(["test.scss:out.css"])).shouldExit(0); + await d.file("out.css", anything).validate(); + + await d.file("test.scss", "a {b: }").create(); + var sass = await update(["--no-error-css", "test.scss:out.css"]); + expect(sass.stderr, emits("Error: Expected expression.")); + expect(sass.stderr, emitsThrough(contains("test.scss 1:7"))); + await sass.shouldExit(65); + + await d.nothing("out.css").validate(); + }); + group("doesn't allow", () { test("--stdin", () async { var sass = await update(["--stdin", "test.scss"]); diff --git a/test/cli/shared/watch.dart b/test/cli/shared/watch.dart index a41f7966..59147e3c 100644 --- a/test/cli/shared/watch.dart +++ b/test/cli/shared/watch.dart @@ -65,24 +65,51 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("out.css", "x {y: z}").validate(); }); - test("continues compiling after an error", () async { - await d.file("test1.scss", "a {b: }").create(); - await d.file("test2.scss", "x {y: z}").create(); + group("continues compiling after an error", () { + test("with --error-css", () async { + await d.file("test1.scss", "a {b: }").create(); + await d.file("test2.scss", "x {y: z}").create(); - var sass = - await watch(["test1.scss:out1.css", "test2.scss:out2.css"]); - await expectLater(sass.stderr, emits('Error: Expected expression.')); - await expectLater( - sass.stderr, emitsThrough(contains('test1.scss 1:7'))); - await expectLater( - sass.stdout, emitsThrough('Compiled test2.scss to out2.css.')); - await expectLater(sass.stdout, _watchingForChanges); - await sass.kill(); + var message = 'Error: Expected expression.'; + var sass = + await watch(["test1.scss:out1.css", "test2.scss:out2.css"]); + await expectLater(sass.stderr, emits(message)); + await expectLater( + sass.stderr, emitsThrough(contains('test1.scss 1:7'))); + await expectLater( + sass.stdout, emitsThrough('Compiled test2.scss to out2.css.')); + await expectLater(sass.stdout, _watchingForChanges); + await sass.kill(); - await d.nothing("out1.css").validate(); - await d - .file("out2.css", equalsIgnoringWhitespace("x { y: z; }")) - .validate(); + await d.file("out1.css", contains(message)).validate(); + await d + .file("out2.css", equalsIgnoringWhitespace("x { y: z; }")) + .validate(); + }); + + test("with --no-error-css", () async { + await d.file("test1.scss", "a {b: }").create(); + await d.file("test2.scss", "x {y: z}").create(); + + var sass = await watch([ + "--no-error-css", + "test1.scss:out1.css", + "test2.scss:out2.css" + ]); + await expectLater( + sass.stderr, emits('Error: Expected expression.')); + await expectLater( + sass.stderr, emitsThrough(contains('test1.scss 1:7'))); + await expectLater( + sass.stdout, emitsThrough('Compiled test2.scss to out2.css.')); + await expectLater(sass.stdout, _watchingForChanges); + await sass.kill(); + + await d.nothing("out1.css").validate(); + await d + .file("out2.css", equalsIgnoringWhitespace("x { y: z; }")) + .validate(); + }); }); test("stops compiling after an error with --stop-on-error", () async { @@ -94,16 +121,18 @@ void sharedTests(Future runSass(Iterable arguments)) { "test1.scss:out1.css", "test2.scss:out2.css" ]); + + var message = 'Error: Expected expression.'; await expectLater( sass.stderr, emitsInOrder([ - 'Error: Expected expression.', + message, emitsThrough(contains('test1.scss 1:7')), emitsDone ])); await sass.shouldExit(65); - await d.nothing("out1.css").validate(); + await d.file("out1.css", contains(message)).validate(); await d.nothing("out2.css").validate(); }); }); @@ -201,13 +230,14 @@ void sharedTests(Future runSass(Iterable arguments)) { await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); + var message = 'Error: Expected expression.'; await d.file("test.scss", "a {b: }").create(); - await expectLater(sass.stderr, emits('Error: Expected expression.')); + await expectLater(sass.stderr, emits(message)); await expectLater( sass.stderr, emitsThrough(contains('test.scss 1:7'))); await sass.kill(); - await d.nothing("out.css").validate(); + await d.file("out.css", contains(message)).validate(); }); test("stops compiling after an error with --stop-on-error", () async { @@ -219,21 +249,22 @@ void sharedTests(Future runSass(Iterable arguments)) { await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); + var message = 'Error: Expected expression.'; await d.file("test.scss", "a {b: }").create(); await expectLater( sass.stderr, emitsInOrder([ - 'Error: Expected expression.', + message, emitsThrough(contains('test.scss 1:7')), emitsDone ])); await sass.shouldExit(65); - await d.nothing("out.css").validate(); + await d.file("out.css", contains(message)).validate(); }); group("when its dependency is deleted", () { - test("and removes the output", () async { + test("and updates the output", () async { await d.file("_other.scss", "a {b: c}").create(); await d.file("test.scss", "@import 'other'").create(); @@ -243,14 +274,14 @@ void sharedTests(Future runSass(Iterable arguments)) { await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); + var message = "Error: Can't find stylesheet to import."; d.file("_other.scss").io.deleteSync(); - await expectLater( - sass.stderr, emits("Error: Can't find stylesheet to import.")); + await expectLater(sass.stderr, emits(message)); await expectLater( sass.stderr, emitsThrough(contains('test.scss 1:9'))); await sass.kill(); - await d.nothing("out.css").validate(); + await d.file("out.css", contains(message)).validate(); }); test("but another is available", () async { @@ -374,13 +405,12 @@ void sharedTests(Future runSass(Iterable arguments)) { await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); + var message = "Error: It's not clear which file to import. Found:"; await d.file("_other.sass", "x\n y: z").create(); - await expectLater(sass.stderr, - emits("Error: It's not clear which file to import. Found:")); - await expectLater(sass.stdout, emits("Deleted out.css.")); + await expectLater(sass.stderr, emits(message)); await sass.kill(); - await d.nothing("out.css").validate(); + await d.file("out.css", contains(message)).validate(); }); group("that overrides the previous dependency", () { From 9ff1e20064b66918c7cc3f9fc9f5f590b762b927 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 3 May 2019 12:55:53 -0700 Subject: [PATCH 23/27] Run app-snapshot in the before-test grinder task (#660) This matches the behavior recommended by the tests if the snapshot is out-of-date. --- tool/grind.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tool/grind.dart b/tool/grind.dart index 16f42290..c06f821e 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -5,6 +5,7 @@ import 'package:grinder/grinder.dart'; import 'grind/npm.dart'; +import 'grind/standalone.dart'; import 'grind/synchronize.dart'; export 'grind/bazel.dart'; @@ -35,5 +36,5 @@ format() { npmInstall() => run("npm", arguments: ["install"]); @Task('Runs the tasks that are required for running tests.') -@Depends(format, synchronize, npmPackage, npmInstall) +@Depends(format, synchronize, npmPackage, npmInstall, appSnapshot) beforeTest() {} From 49c05623d96056c7e1773e5ee6a565933364ddef Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 3 May 2019 13:09:31 -0700 Subject: [PATCH 24/27] Use Node.js when deploying to npm Without this, we won't actually have npm installed. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index df9f1f11..e8f99b3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -134,6 +134,8 @@ jobs: # Deploy to npm. - if: *deploy-if script: skip + language: node_js + node_js: stable deploy: provider: script script: tool/travis/deploy/npm.sh From 117b1b7f91b9fda5727c50949f48531f1b2359c3 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 3 May 2019 13:12:38 -0700 Subject: [PATCH 25/27] Add names to deploy steps --- .travis.yml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index e8f99b3f..6a763cd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -105,6 +105,7 @@ jobs: # Deploy Linux and Windows releases to GitHub. Mac OS releases are deployed in # a later stage so that we can build application snapshots on Mac OS bots. - stage: deploy 1 + name: GitHub: Windows and Linux if: *deploy-if env: &github-env - GITHUB_USER=sassbot @@ -132,7 +133,8 @@ jobs: on: {tags: true} # Deploy to npm. - - if: *deploy-if + - name: npm + if: *deploy-if script: skip language: node_js node_js: stable @@ -143,7 +145,8 @@ jobs: on: {tags: true} # Deploy to pub. - - if: *deploy-if + - name: pub + if: *deploy-if script: skip deploy: provider: script @@ -152,7 +155,8 @@ jobs: on: {tags: true} # Deploy to Homebrew. - - if: *deploy-if + - name: Homebrew + if: *deploy-if env: *github-env script: skip deploy: @@ -162,7 +166,8 @@ jobs: on: {tags: true} # Deploy to Chocolatey. - - if: *deploy-if + - name: Chocolatey + if: *deploy-if env: # CHOCO_TOKEN="..." - secure: "cW11kQYBBEElfVsc1pJfVEHOMYwt0ZK+9STZHwSPbAISlplIRnsimMN7TqCY2aLnkWXyUMU7DphIl9uQ86M4BT1bJopsHbapj27bFSlKWHlBSDB/xylFHywV41Yk5lMlr8DLMbsSzVahasyR34xS6HYIRlDpZ9TFiQuDQNJxQmqTZJg/FC+3nqCI7tyMKGkWc48ikTcmqDMHsG9CudG2u+Q3S9sLNXArh9T4tSnAyWkTvSrS05mvFx5tC83PcG9/VkioTId+VRSJchwTmCxDFDROrTikTXZMtYn8wMAQ2wQ34TQXNZMZ9uiHA6W0IuJV2EnYerJbqV2lrJq9xqZywKu6HW6i4GhrCvizALNFZx/N7s/10xuf3UcuWizYml/e0MYT+6t4ojTYBMKv+Cx+H2Y2Jdpvdn2ZAIl6LaU3pLw4OIPJ7aXjDwZd63MPxtwGwVLHbH7Zu+oUv1erIq5LtatuocGWipD8WdiMBQvyCuDRMowpLPoAbj+mevOf+xlY2Eym4tOXpxM7iY3lXFHROo5dQbhsARfVF9J1gl5PuYXvCjxqTfK/ef9t3ZoDbi57+yAJUWlZfWa5r1zKE8OS0pA8GfQRLom/Lt0wKVw4Xiofgolzd9pEHi4JpsYIQb8O+u1ACQU6nBCS87CGrQ+ylnzKfGUs0aW2K3gvbkg0LUg=" @@ -174,7 +179,8 @@ jobs: on: {tags: true} # Redeploy sass-lang.com. - - if: *deploy-if + - name: sass-lang.com + if: *deploy-if env: # HEROKU_TOKEN="..." - secure: "JUBfLfJr+5RIvxkk+1qqtyJxaHq2A9x78G9L9bUTjBD3C7XJOmJtt8u+pAgperL+fIfrlcAVzmYTyRGLj+0PL4VcxMTrmn604FIn+ffcgatylYKtPdpJq5dnXQ4U0+ROaZnq1mPZeUI7OHwRlVcQStIWkPxk7Eno+ZqxFyziLZ4wxo7l809iCAbhJttCfWPupyHX75cHhBMFaPEk9LYUrxQURyE0GKMQgDKHY/ehe87gyuKt3o79gi7/E0chQApv1jeua4xz5tyNBNQH/nwuJmmBCab/IdgTLn2vj4qjT1ENeB5PINCfFOT98IPVREOLbY+jiGRVwQFJbC55mOBQH21mfIt/XLhcjYHe80X4PyYhtJJ2GDadcyrzppZmHpEDHzfR8B29LEKhNM1VfUTZ8I9+TudXW8uMjtqCsXyi8bKZLsuZNNSlWVh1qIR+FXMDTrYNOnTcvgzv5yi6fbD10Uf8k1G0pHtKQiFainWatmJhNIMtGoYe7LRAB0Rj7OGWDMv/PHy/+Z7BKIj3b0LefVN1xpeuy3mMhMq9g5Q8HI8yk37DNmZQ9kwgHpIUk/t2xAdwzZ0XMSDFW9iHV48/iHwi0t5M2RCFRnI8ZaUNU5Z8QLUPHnazCucIvR4N8ns1yFwDgNQ5CzlBFrV70EwgqZhjjToAOhnIXpHMWr3AVAw=" @@ -190,6 +196,7 @@ jobs: # Deploy to Bazel. This is in a separate deploy stage because it needs to # install the npm package. - stage: deploy 2 + name: Bazel if: *deploy-if env: *github-env script: skip @@ -199,7 +206,8 @@ jobs: skip_cleanup: true on: {tags: true} - - if: *deploy-if + - name: GitHub: Mac OS + if: *deploy-if env: *github-env script: skip deploy: From 60fc306ab47a1b48f99c5ca9c4744bef25f0fce4 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 3 May 2019 13:13:20 -0700 Subject: [PATCH 26/27] Release 1.20.1 This doesn't have any code changes, but I want to verify that the new deployment infrastructure works and the only way to do that is to actually deploy. --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dbd3943..7e8efefb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.20.1 + +* No user-visible changes. + ## 1.20.0 * Support attribute selector modifiers, such as the `i` in `[title="test" i]`. diff --git a/pubspec.yaml b/pubspec.yaml index cb42f299..39ee7505 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.20.0 +version: 1.20.1 description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass From aa917901b6b5a1d28d2a18d25da0fcbf15f9edec Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 3 May 2019 14:06:19 -0700 Subject: [PATCH 27/27] Fix YAML syntax errors (#667) --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6a763cd2..a1875e34 100644 --- a/.travis.yml +++ b/.travis.yml @@ -105,7 +105,7 @@ jobs: # Deploy Linux and Windows releases to GitHub. Mac OS releases are deployed in # a later stage so that we can build application snapshots on Mac OS bots. - stage: deploy 1 - name: GitHub: Windows and Linux + name: "GitHub: Windows and Linux" if: *deploy-if env: &github-env - GITHUB_USER=sassbot @@ -206,7 +206,7 @@ jobs: skip_cleanup: true on: {tags: true} - - name: GitHub: Mac OS + - name: "GitHub: Mac OS" if: *deploy-if env: *github-env script: skip