Merge pull request #670 from sass/merge-master

Merge master into feature.use
This commit is contained in:
Natalie Weizenbaum 2019-05-07 12:34:40 -07:00 committed by GitHub
commit 02611dfae4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1134 additions and 386 deletions

View File

@ -1,34 +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
- 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
- TASK=tests NODE_VERSION=lts/carbon
# 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:
@ -43,44 +21,91 @@ cache:
directories:
- $HOME/.pub-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"
- unzip dart.zip
- export PATH="$PATH:`pwd`/dart-sdk/bin"
- 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
# 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
## Deployment
before_install:
- tool/travis/use_dart.sh
- export PATH="$PATH:`pwd`/dart-sdk/bin";
jobs:
include:
## Testing
# Language specs, defined in sass/sass-spec. These need Ruby to run the spec
# runner.
- &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
# 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
# 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.
- &node-tests
name: Node tests | Dart stable | Node stable
language: node_js
node_js: stable
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.
- name: static analysis
language: dart
dart_task: {dartanalyzer: --fatal-warnings lib tool test}
- name: code formatting
language: dart
dart_task: dartfmt
## Deployment
# Sanity check before releasing anywhere.
- stage: sanity check
if: &deploy-if
(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
name: "GitHub: Windows and Linux"
if: *deploy-if
env: &github-env
- GITHUB_USER=sassbot
@ -92,7 +117,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
@ -108,8 +133,11 @@ jobs:
on: {tags: true}
# Deploy to npm.
- if: *deploy-if
- name: npm
if: *deploy-if
script: skip
language: node_js
node_js: stable
deploy:
provider: script
script: tool/travis/deploy/npm.sh
@ -117,7 +145,8 @@ jobs:
on: {tags: true}
# Deploy to pub.
- if: *deploy-if
- name: pub
if: *deploy-if
script: skip
deploy:
provider: script
@ -126,7 +155,8 @@ jobs:
on: {tags: true}
# Deploy to Homebrew.
- if: *deploy-if
- name: Homebrew
if: *deploy-if
env: *github-env
script: skip
deploy:
@ -136,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="
@ -148,10 +179,11 @@ jobs:
on: {tags: true}
# Redeploy sass-lang.com.
- if: *deploy-if
- name: sass-lang.com
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:
@ -164,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
@ -172,3 +205,13 @@ jobs:
script: pub run grinder update-bazel
skip_cleanup: true
on: {tags: true}
- name: "GitHub: Mac OS"
if: *deploy-if
env: *github-env
script: skip
deploy:
provider: script
script: pub run grinder github-mac-os
skip_cleanup: true
on: {tags: true}

View File

@ -1,3 +1,70 @@
## 1.20.1
* No user-visible changes.
## 1.20.0
* 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.
### 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
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.
### 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
practice.
## 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.
### 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

View File

@ -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

View File

@ -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<Callable> 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<String> 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<String> 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;
}

View File

@ -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<T>(SelectorVisitor<T> 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].

View File

@ -36,7 +36,8 @@ Future<CompileResult> 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<CompileResult> 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<CompileResult> 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<CompileResult> compileStringAsync(String source,
useSpaces,
indentWidth,
lineFeed,
sourceMap);
sourceMap,
charset);
}
/// Compiles [stylesheet] and returns its result.
@ -116,7 +120,8 @@ Future<CompileResult> _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<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

View File

@ -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

View File

@ -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.

View File

@ -69,7 +69,9 @@ main(List<String> 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.
}

View File

@ -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,39 +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)
: await compileAsync(source,
syntax: syntax,
logger: options.logger,
importCache: importCache,
style: options.style,
sourceMap: options.emitSourceMap);
} else {
result = source == null
? compileString(await readStdin(),
syntax: syntax,
logger: options.logger,
importCache: graph.importCache,
importer: FilesystemImporter('.'),
style: options.style,
sourceMap: options.emitSourceMap)
: compile(source,
syntax: syntax,
logger: options.logger,
importCache: graph.importCache,
style: options.style,
sourceMap: options.emitSourceMap);
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;

View File

@ -55,6 +55,13 @@ 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('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);
@ -171,6 +178,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<String> get loadPaths => _options['load-path'] as List<String>;
@ -193,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.
///
@ -356,7 +372,7 @@ class ExecutableOptions {
/// [destination] directories.
Map<String, String> _listSourceDirectory(String source, String destination) {
var map = <String, String>{};
for (var path in listDir(source)) {
for (var path in listDir(source, recursive: true)) {
var basename = p.basename(path);
if (basename.startsWith("_")) continue;

View File

@ -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;

View File

@ -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<Uri> canonicalize(Uri url);
/// Loads the Sass text for the given [url], or returns `null` if

View File

@ -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;

View File

@ -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);

View File

@ -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<String> 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<String> listDir(String path, {bool recursive = false}) => null;
/// Returns the modification time of the file at [path].
DateTime modificationTime(String path) => null;

View File

@ -191,14 +191,23 @@ void ensureDir(String path) {
});
}
Iterable<String> listDir(String path) {
Iterable<String> list(String parent) =>
_fs.readdirSync(parent).expand((child) {
var path = p.join(parent, child as String);
return dirExists(path) ? listDir(path) : [path];
});
Iterable<String> 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<String> 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) =>

View File

@ -71,10 +71,11 @@ bool dirExists(String path) => io.Directory(path).existsSync();
void ensureDir(String path) => io.Directory(path).createSync(recursive: true);
Iterable<String> listDir(String path) => io.Directory(path)
.listSync(recursive: true)
.where((entity) => entity is io.File)
.map((entity) => entity.path);
Iterable<String> 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);
@ -93,7 +94,7 @@ Future<Stream<WatchEvent>> watchDir(String path, {bool poll = false}) async {
// triggers but the caller can still listen at their leisure.
var stream = SubscriptionStream<WatchEvent>(watcher.events
.transform(const SingleSubscriptionTransformer<WatchEvent, WatchEvent>())
.listen((e) => print(e)));
.listen(null));
await watcher.ready;
return stream;

View File

@ -56,7 +56,8 @@ void main() {
Map: mapConstructor,
Null: nullConstructor,
Number: numberConstructor,
String: stringConstructor);
String: stringConstructor,
Error: jsErrorConstructor);
}
/// Converts Sass to CSS.

View File

@ -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});
}

View File

@ -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) =>

View File

@ -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.";
}

View File

@ -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()) {

View File

@ -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;

View File

@ -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.

View File

@ -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) {

View File

@ -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;

View File

@ -29,6 +29,7 @@ import '../extend/extension.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';
@ -1063,7 +1064,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.";

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/synchronize.dart for details.
//
// Checksum: 4de442e73c75611a675e2fd0d8962219f1ceffd7
// Checksum: ef520a902171c8d105ece12e7c84889e34f95d80
//
// ignore_for_file: unused_import
@ -38,6 +38,7 @@ import '../extend/extension.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';
@ -1065,7 +1066,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.";

View File

@ -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<T> extends RecursiveStatementVisitor<T>
implements ExpressionVisitor<T> {
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;
}

View File

@ -137,6 +137,8 @@ abstract class RecursiveStatementVisitor<T> implements StatementVisitor<T> {
return visitChildren(node);
}
T visitUseRule(UseRule node) => null;
T visitVariableDeclaration(VariableDeclaration node) {
visitExpression(node.expression);
return null;

View File

@ -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 {
@ -910,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);
}

View File

@ -4,8 +4,9 @@
"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",
"fibers": ">=1.0.0 <5.0.0",
"intercept-stdout": "^0.1.2"
}
}

View File

@ -1,5 +1,5 @@
name: sass
version: 1.17.3-dev
version: 1.20.1
description: A Sass implementation in Dart.
author: Dart Team <misc@dartlang.org>
homepage: https://github.com/sass/dart-sass

View File

@ -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);
});
}

View File

@ -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';

View File

@ -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';

View File

@ -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);
});
}

View File

@ -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';

View File

@ -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';

View File

@ -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,137 @@ 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)));
});
});
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();
});
});
});
}

View File

@ -72,13 +72,14 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> 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<TestProcess> runSass(Iterable<String> 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();
});

View File

@ -113,21 +113,4 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> 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);
});
}

View File

@ -153,12 +153,13 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> 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<TestProcess> runSass(Iterable<String> 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<TestProcess> runSass(Iterable<String> 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"]);

View File

@ -65,24 +65,51 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> 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<TestProcess> runSass(Iterable<String> 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<TestProcess> runSass(Iterable<String> 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<TestProcess> runSass(Iterable<String> 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<TestProcess> runSass(Iterable<String> 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<TestProcess> runSass(Iterable<String> 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", () {

View File

@ -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", () {

View File

@ -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";')
@ -169,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:👭}"));
});
});
});
}

View File

@ -75,6 +75,7 @@ class SassTypes {
external NodeSassNullClass get Null;
external Function get Number;
external Function get String;
external Function get Error;
}
@JS()

View File

@ -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()}",

26
test/output_test.dart Normal file
View File

@ -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 */"));
});
});
}

View File

@ -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.");
});
});
}

View File

@ -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() {}

View File

@ -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<void> _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() {

View File

@ -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<int>;
// 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<int>;
// 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();
}

7
tool/travis/task/dart_tests.sh Executable file
View File

@ -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

7
tool/travis/task/node_tests.sh Executable file
View File

@ -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

8
tool/travis/task/specs.sh Executable file
View File

@ -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)

View File

@ -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 "$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

24
tool/travis/use_dart.sh Executable file
View File

@ -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