From c9f0d3f62317101776a55b828f4cf8cd67cf24e8 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 15 May 2024 12:49:27 -0700 Subject: [PATCH 1/3] Fix deprecation flags in the CLI and add tests Closes #2244 --- CHANGELOG.md | 9 + lib/src/executable/compile_stylesheet.dart | 4 + lib/src/executable/repl.dart | 12 +- pkg/sass_api/CHANGELOG.md | 4 + pkg/sass_api/pubspec.yaml | 4 +- pubspec.yaml | 2 +- test/cli/dart/deprecations_test.dart | 15 + test/cli/node/deprecations_test.dart | 17 + test/cli/shared/deprecations.dart | 497 +++++++++++++++++++++ 9 files changed, 558 insertions(+), 6 deletions(-) create mode 100644 test/cli/dart/deprecations_test.dart create mode 100644 test/cli/node/deprecations_test.dart create mode 100644 test/cli/shared/deprecations.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c306b1ac..c932688b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 1.77.2 + +### Command-Line Interface + +* Properly handle the `--silence-deprecation` flag. + +* Handle the `--fatal-deprecation` and `--future-deprecation` flags for + `--interactive` mode. + ## 1.77.1 * Fix a crash that could come up with importers in certain contexts. diff --git a/lib/src/executable/compile_stylesheet.dart b/lib/src/executable/compile_stylesheet.dart index cd121a6f5..3dbf3dbe0 100644 --- a/lib/src/executable/compile_stylesheet.dart +++ b/lib/src/executable/compile_stylesheet.dart @@ -110,6 +110,7 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, verbose: options.verbose, sourceMap: options.emitSourceMap, charset: options.charset, + silenceDeprecations: options.silenceDeprecations, fatalDeprecations: options.fatalDeprecations, futureDeprecations: options.futureDeprecations) : await compileAsync(source, @@ -121,6 +122,7 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, verbose: options.verbose, sourceMap: options.emitSourceMap, charset: options.charset, + silenceDeprecations: options.silenceDeprecations, fatalDeprecations: options.fatalDeprecations, futureDeprecations: options.futureDeprecations); } else { @@ -135,6 +137,7 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, verbose: options.verbose, sourceMap: options.emitSourceMap, charset: options.charset, + silenceDeprecations: options.silenceDeprecations, fatalDeprecations: options.fatalDeprecations, futureDeprecations: options.futureDeprecations) : compile(source, @@ -146,6 +149,7 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, verbose: options.verbose, sourceMap: options.emitSourceMap, charset: options.charset, + silenceDeprecations: options.silenceDeprecations, fatalDeprecations: options.fatalDeprecations, futureDeprecations: options.futureDeprecations); } diff --git a/lib/src/executable/repl.dart b/lib/src/executable/repl.dart index e2e858a26..f79e2de33 100644 --- a/lib/src/executable/repl.dart +++ b/lib/src/executable/repl.dart @@ -12,6 +12,7 @@ import '../exception.dart'; import '../executable/options.dart'; import '../import_cache.dart'; import '../importer/filesystem.dart'; +import '../logger/deprecation_processing.dart'; import '../logger/tracking.dart'; import '../parse/parser.dart'; import '../utils.dart'; @@ -20,7 +21,12 @@ import '../visitor/evaluate.dart'; /// Runs an interactive SassScript shell according to [options]. Future repl(ExecutableOptions options) async { var repl = Repl(prompt: '>> '); - var logger = TrackingLogger(options.logger); + var trackingLogger = TrackingLogger(options.logger); + var logger = DeprecationProcessingLogger(trackingLogger, + silenceDeprecations: options.silenceDeprecations, + fatalDeprecations: options.fatalDeprecations, + futureDeprecations: options.futureDeprecations, + limitRepetition: !options.verbose); var evaluator = Evaluator( importer: FilesystemImporter.cwd, importCache: ImportCache( @@ -46,8 +52,8 @@ Future repl(ExecutableOptions options) async { print(evaluator.evaluate(Expression.parse(line, logger: logger))); } } on SassException catch (error, stackTrace) { - _logError( - error, getTrace(error) ?? stackTrace, line, repl, options, logger); + _logError(error, getTrace(error) ?? stackTrace, line, repl, options, + trackingLogger); } } } diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index 63e2b58f8..c0839df75 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,7 @@ +## 10.4.2 + +* No user-visible changes. + ## 10.4.1 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 0db04d6b3..08eda2c49 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -2,7 +2,7 @@ name: sass_api # Note: Every time we add a new Sass AST node, we need to bump the *major* # version because it's a breaking change for anyone who's implementing the # visitor interface(s). -version: 10.4.1 +version: 10.4.2 description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - sass: 1.77.1 + sass: 1.77.2 dev_dependencies: dartdoc: ^6.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index 4ecb69a9a..47cec4e93 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.77.1 +version: 1.77.2 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass diff --git a/test/cli/dart/deprecations_test.dart b/test/cli/dart/deprecations_test.dart new file mode 100644 index 000000000..4b4a4244f --- /dev/null +++ b/test/cli/dart/deprecations_test.dart @@ -0,0 +1,15 @@ +// Copyright 2024 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. + +@TestOn('vm') + +import 'package:test/test.dart'; + +import '../dart_test.dart'; +import '../shared/deprecations.dart'; + +void main() { + setUpAll(ensureSnapshotUpToDate); + sharedTests(runSass); +} diff --git a/test/cli/node/deprecations_test.dart b/test/cli/node/deprecations_test.dart new file mode 100644 index 000000000..88e383d2b --- /dev/null +++ b/test/cli/node/deprecations_test.dart @@ -0,0 +1,17 @@ +// Copyright 2024 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. + +@TestOn('vm') +@Tags(['node']) + +import 'package:test/test.dart'; + +import '../../ensure_npm_package.dart'; +import '../node_test.dart'; +import '../shared/deprecations.dart'; + +void main() { + setUpAll(ensureNpmPackage); + sharedTests(runSass); +} diff --git a/test/cli/shared/deprecations.dart b/test/cli/shared/deprecations.dart new file mode 100644 index 000000000..03f1b4dc2 --- /dev/null +++ b/test/cli/shared/deprecations.dart @@ -0,0 +1,497 @@ +// Copyright 2024 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; +import 'package:test_process/test_process.dart'; + +/// Defines test that are shared between the Dart and Node.js CLI test suites. +void sharedTests(Future runSass(Iterable arguments)) { + // Test complaining about invalid deprecations, combinations, etc + + group("--silence-deprecation", () { + group("prints a warning", () { + setUp(() => d.file("test.scss", "").create()); + + test("for user-authored", () async { + var sass = + await runSass(["--silence-deprecation=user-authored", "test.scss"]); + expect(sass.stderr, emits(contains("User-authored deprecations"))); + await sass.shouldExit(0); + }); + + test("for an obsolete deprecation", () async { + // TODO: test this when a deprecation is obsoleted + }); + + test("for an inactive future deprecation", () async { + var sass = await runSass(["--silence-deprecation=import", "test.scss"]); + expect(sass.stderr, emits(contains("Future import deprecation"))); + await sass.shouldExit(0); + }); + + test("for an active future deprecation", () async { + var sass = await runSass([ + "--future-deprecation=import", + "--silence-deprecation=import", + "test.scss" + ]); + expect(sass.stderr, emits(contains("Conflicting options for future"))); + await sass.shouldExit(0); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--silence-deprecation=user-authored", + "test.scss:out.css" + ]); + expect(sass.stderr, emits(contains("User-authored deprecations"))); + + await expectLater(sass.stdout, + emitsThrough(endsWith('Compiled test.scss to out.css.'))); + await sass.kill(); + }); + + test("in repl mode", () async { + var sass = await runSass( + ["--interactive", "--silence-deprecation=user-authored"]); + await expectLater( + sass.stderr, emits(contains("User-authored deprecations"))); + await sass.kill(); + }); + }); + + group("throws an error for an unknown deprecation", () { + setUp(() => d.file("test.scss", "").create()); + + test("in immediate mode", () async { + var sass = + await runSass(["--silence-deprecation=unknown", "test.scss"]); + expect(sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + await sass.shouldExit(64); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--silence-deprecation=unknown", + "test.scss:out.css" + ]); + expect(sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + await sass.shouldExit(64); + }); + + test("in repl mode", () async { + var sass = + await runSass(["--interactive", "--silence-deprecation=unknown"]); + expect(sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + await sass.shouldExit(64); + }); + }); + + group("silences", () { + group("a parse-time deprecation", () { + setUp( + () => d.file("test.scss", "@if true {} @elseif false {}").create()); + + test("in immediate mode", () async { + var sass = + await runSass(["--silence-deprecation=elseif", "test.scss"]); + expect(sass.stderr, emitsDone); + await sass.shouldExit(0); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--silence-deprecation=elseif", + "test.scss:out.css" + ]); + expect(sass.stderr, emitsDone); + + await expectLater(sass.stdout, + emitsThrough(endsWith('Compiled test.scss to out.css.'))); + await sass.kill(); + }); + + test("in repl mode", () async { + var sass = await runSass( + ["--interactive", "--silence-deprecation=strict-unary"]); + expect(sass.stderr, emitsDone); + sass.stdin.writeln("4 -(5)"); + await expectLater(sass.stdout, emitsInOrder([">> 4 -(5)", "-1"])); + await sass.kill(); + }); + }); + + group("an evaluation-time deprecation", () { + setUp(() => d.file("test.scss", """ + @use 'sass:math'; + a {b: math.random(1px)} + """).create()); + + test("in immediate mode", () async { + var sass = await runSass( + ["--silence-deprecation=function-units", "test.scss"]); + expect(sass.stderr, emitsDone); + await sass.shouldExit(0); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--silence-deprecation=function-units", + "test.scss:out.css" + ]); + expect(sass.stderr, emitsDone); + + await expectLater(sass.stdout, + emitsThrough(endsWith('Compiled test.scss to out.css.'))); + await sass.kill(); + }); + + test("in repl mode", () async { + var sass = await runSass( + ["--interactive", "--silence-deprecation=function-units"]); + expect(sass.stderr, emitsDone); + sass.stdin.writeln("@use 'sass:math'"); + await expectLater(sass.stdout, emits(">> @use 'sass:math'")); + sass.stdin.writeln("math.random(1px)"); + await expectLater( + sass.stdout, emitsInOrder([">> math.random(1px)", "1"])); + await sass.kill(); + }); + }); + }); + }); + + group("--fatal-deprecation", () { + group("prints a warning", () { + setUp(() => d.file("test.scss", "").create()); + + test("for an obsolete deprecation", () async { + // TODO: test this when a deprecation is obsoleted + }); + + test("for an inactive future deprecation", () async { + var sass = await runSass(["--fatal-deprecation=import", "test.scss"]); + expect(sass.stderr, emits(contains("Future import deprecation"))); + await sass.shouldExit(0); + }); + + test("for a silent deprecation", () async { + var sass = await runSass([ + "--fatal-deprecation=elseif", + "--silence-deprecation=elseif", + "test.scss" + ]); + expect(sass.stderr, emits(contains("Ignoring setting to silence"))); + await sass.shouldExit(0); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--fatal-deprecation=elseif", + "--silence-deprecation=elseif", + "test.scss:out.css" + ]); + expect(sass.stderr, emits(contains("Ignoring setting to silence"))); + + await expectLater(sass.stdout, + emitsThrough(endsWith('Compiled test.scss to out.css.'))); + await sass.kill(); + }); + + test("in repl mode", () async { + var sass = await runSass([ + "--interactive", + "--fatal-deprecation=elseif", + "--silence-deprecation=elseif" + ]); + await expectLater( + sass.stderr, emits(contains("Ignoring setting to silence"))); + await sass.kill(); + }); + }); + + group("throws an error for", () { + group("an unknown deprecation", () { + setUp(() => d.file("test.scss", "").create()); + + test("in immediate mode", () async { + var sass = + await runSass(["--fatal-deprecation=unknown", "test.scss"]); + expect( + sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + await sass.shouldExit(64); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--fatal-deprecation=unknown", + "test.scss:out.css" + ]); + expect( + sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + await sass.shouldExit(64); + }); + + test("in repl mode", () async { + var sass = + await runSass(["--interactive", "--fatal-deprecation=unknown"]); + expect( + sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + await sass.shouldExit(64); + }); + }); + + group("a parse-time deprecation", () { + setUp( + () => d.file("test.scss", "@if true {} @elseif false {}").create()); + + test("in immediate mode", () async { + var sass = await runSass(["--fatal-deprecation=elseif", "test.scss"]); + expect(sass.stderr, emits(startsWith("Error: "))); + await sass.shouldExit(65); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--fatal-deprecation=elseif", + "test.scss:out.css" + ]); + await expectLater(sass.stderr, emits(startsWith("Error: "))); + await expectLater( + sass.stdout, + emitsInOrder( + ["Sass is watching for changes. Press Ctrl-C to stop.", ""])); + await sass.kill(); + }); + + test("in repl mode", () async { + var sass = await runSass( + ["--interactive", "--fatal-deprecation=strict-unary"]); + sass.stdin.writeln("4 -(5)"); + await expectLater( + sass.stdout, + emitsInOrder([ + ">> 4 -(5)", + emitsThrough(startsWith("Error: ")), + emitsThrough(contains("Remove this setting")) + ])); + + // Verify that there's no output written for the previous line. + sass.stdin.writeln("1"); + await expectLater(sass.stdout, emitsInOrder([">> 1", "1"])); + await sass.kill(); + }); + }); + + group("an evaluation-time deprecation", () { + setUp(() => d.file("test.scss", """ + @use 'sass:math'; + a {b: math.random(1px)} + """).create()); + + test("in immediate mode", () async { + var sass = await runSass( + ["--fatal-deprecation=function-units", "test.scss"]); + expect(sass.stderr, emits(startsWith("Error: "))); + await sass.shouldExit(65); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--fatal-deprecation=function-units", + "test.scss:out.css" + ]); + await expectLater(sass.stderr, emits(startsWith("Error: "))); + await expectLater( + sass.stdout, + emitsInOrder( + ["Sass is watching for changes. Press Ctrl-C to stop.", ""])); + await sass.kill(); + }); + + test("in repl mode", () async { + var sass = await runSass( + ["--interactive", "--fatal-deprecation=function-units"]); + sass.stdin.writeln("@use 'sass:math'"); + await expectLater(sass.stdout, emits(">> @use 'sass:math'")); + sass.stdin.writeln("math.random(1px)"); + await expectLater( + sass.stdout, + emitsInOrder([ + ">> math.random(1px)", + emitsThrough(startsWith("Error: ")), + emitsThrough(contains("Remove this setting")) + ])); + + // Verify that there's no output written for the previous line. + sass.stdin.writeln("1"); + await expectLater(sass.stdout, emitsInOrder([">> 1", "1"])); + await sass.kill(); + }); + }); + }); + }); + + group("--future-deprecation", () { + group("prints a warning for", () { + group("an active deprecation", () { + setUp(() => d.file("test.scss", "").create()); + + test("in immediate mode", () async { + var sass = await runSass( + ["--future-deprecation=function-units", "test.scss"]); + expect(sass.stderr, + emits(contains("function-units is not a future deprecation"))); + await sass.shouldExit(0); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--future-deprecation=function-units", + "test.scss:out.css" + ]); + expect(sass.stderr, + emits(contains("function-units is not a future deprecation"))); + + await expectLater(sass.stdout, + emitsThrough(endsWith('Compiled test.scss to out.css.'))); + await sass.kill(); + }); + + test("in repl mode", () async { + // TODO: test this when there's an expression-level future deprecation + }); + }); + + group("an obsolete deprecation", () { + // TODO: test this when there are obsolete deprecations + }); + + group("a parse-time deprecation", () { + setUp(() async { + await d.file("test.scss", "@import 'other';").create(); + await d.file("_other.scss", "").create(); + }); + + test("in immediate mode", () async { + var sass = + await runSass(["--future-deprecation=import", "test.scss"]); + expect(sass.stderr, emits(startsWith("DEPRECATION WARNING"))); + await sass.shouldExit(0); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--future-deprecation=import", + "test.scss:out.css" + ]); + + await expectLater( + sass.stderr, emits(startsWith("DEPRECATION WARNING"))); + await sass.kill(); + }); + + test("in repl mode", () async { + // TODO: test this when there's an expression-level future deprecation + }); + }); + + group("an evaluation-time deprecation", () { + // TODO: test this when there's an evaluation-time future deprecation + }); + }); + + group("throws an error for", () { + group("an unknown deprecation", () { + setUp(() => d.file("test.scss", "").create()); + + test("in immediate mode", () async { + var sass = + await runSass(["--future-deprecation=unknown", "test.scss"]); + expect( + sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + await sass.shouldExit(64); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--future-deprecation=unknown", + "test.scss:out.css" + ]); + expect( + sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + await sass.shouldExit(64); + }); + + test("in repl mode", () async { + var sass = + await runSass(["--interactive", "--future-deprecation=unknown"]); + expect( + sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + await sass.shouldExit(64); + }); + }); + + group("a fatal deprecation", () { + setUp(() async { + await d.file("test.scss", "@import 'other';").create(); + await d.file("_other.scss", "").create(); + }); + + test("in immediate mode", () async { + var sass = await runSass([ + "--fatal-deprecation=import", + "--future-deprecation=import", + "test.scss" + ]); + expect(sass.stderr, emits(startsWith("Error: "))); + await sass.shouldExit(65); + }); + + test("in watch mode", () async { + var sass = await runSass([ + "--watch", + "--poll", + "--fatal-deprecation=import", + "--future-deprecation=import", + "test.scss:out.css" + ]); + await expectLater(sass.stderr, emits(startsWith("Error: "))); + await expectLater( + sass.stdout, + emitsInOrder( + ["Sass is watching for changes. Press Ctrl-C to stop.", ""])); + await sass.kill(); + }); + + test("in repl mode", () async { + // TODO: test this when there's an expression-level future deprecation + }); + }); + }); + }); +} From 8c48a013617f96a6582b9c0b069bdab81a83a929 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 15 May 2024 14:16:31 -0700 Subject: [PATCH 2/3] Expand dartdoc range for sass_api to match sass --- pkg/sass_api/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 08eda2c49..e11f303a5 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: sass: 1.77.2 dev_dependencies: - dartdoc: ^6.0.0 + dartdoc: ">=6.0.0 <9.0.0" dependency_overrides: sass: { path: ../.. } From 9a9e48342cccd8f40acc36980a22ffa1c62af3a7 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 15 May 2024 14:34:21 -0700 Subject: [PATCH 3/3] Add a test to ensure the sass_api dartdoc version matches sass --- test/double_check_test.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/double_check_test.dart b/test/double_check_test.dart index 5cc2a2411..ee87b922d 100644 --- a/test/double_check_test.dart +++ b/test/double_check_test.dart @@ -114,6 +114,17 @@ void main() { expect(pkgPubspec.environment!["sdk"], equals(sassPubspec.environment!["sdk"])); }); + + test("matches dartdoc version", () { + // TODO(nweiz): Just use equals() once dart-lang/pubspec_parse#127 lands + // and is released. + var sassDep = sassPubspec.devDependencies["dartdoc"]; + var pkgDep = pkgPubspec.devDependencies["dartdoc"]; + expect(pkgDep, isA()); + expect(sassDep, isA()); + expect((pkgDep as HostedDependency).version, + equals((sassDep as HostedDependency).version)); + }); }); } }