mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-22 22:02:00 +01:00
Merge pull request #670 from sass/merge-master
Merge master into feature.use
This commit is contained in:
commit
02611dfae4
157
.travis.yml
157
.travis.yml
@ -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}
|
||||
|
67
CHANGELOG.md
67
CHANGELOG.md
@ -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
|
||||
|
32
appveyor.yml
32
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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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].
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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) =>
|
||||
|
@ -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;
|
||||
|
@ -56,7 +56,8 @@ void main() {
|
||||
Map: mapConstructor,
|
||||
Null: nullConstructor,
|
||||
Number: numberConstructor,
|
||||
String: stringConstructor);
|
||||
String: stringConstructor,
|
||||
Error: jsErrorConstructor);
|
||||
}
|
||||
|
||||
/// Converts Sass to CSS.
|
||||
|
@ -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});
|
||||
}
|
||||
|
@ -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) =>
|
||||
|
@ -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.";
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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.";
|
||||
|
@ -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.";
|
||||
|
80
lib/src/visitor/recursive_ast.dart
Normal file
80
lib/src/visitor/recursive_ast.dart
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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"]);
|
||||
|
@ -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", () {
|
||||
|
@ -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", () {
|
||||
|
@ -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:👭}"));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ class SassTypes {
|
||||
external NodeSassNullClass get Null;
|
||||
external Function get Number;
|
||||
external Function get String;
|
||||
external Function get Error;
|
||||
}
|
||||
|
||||
@JS()
|
||||
|
@ -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
26
test/output_test.dart
Normal 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 */"));
|
||||
});
|
||||
});
|
||||
}
|
@ -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.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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() {}
|
||||
|
@ -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() {
|
||||
|
@ -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
7
tool/travis/task/dart_tests.sh
Executable 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
7
tool/travis/task/node_tests.sh
Executable 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
8
tool/travis/task/specs.sh
Executable 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)
|
@ -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
24
tool/travis/use_dart.sh
Executable 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
|
Loading…
x
Reference in New Issue
Block a user