From 1073c7b4121dc292887a3be60fbbdde3af0413f5 Mon Sep 17 00:00:00 2001 From: Jennifer Thakar Date: Wed, 29 May 2024 14:23:43 -0700 Subject: [PATCH] Generate deprecations list from the language repo (#2253) This updates the Deprecation enum to be generated from spec/deprecations.yaml in the language repo. --- .pubignore | 2 +- CHANGELOG.md | 8 +++ lib/src/deprecation.dart | 63 ++++++++++++-------- lib/src/parse/stylesheet.dart | 4 +- pkg/sass_api/CHANGELOG.md | 4 ++ pkg/sass_api/pubspec.yaml | 4 +- pubspec.yaml | 2 +- test/double_check_test.dart | 30 +++++++--- tool/grind.dart | 30 +++------- tool/grind/double_check.dart | 5 +- tool/grind/generate_deprecations.dart | 82 +++++++++++++++++++++++++++ tool/grind/utils.dart | 17 ++++++ 12 files changed, 189 insertions(+), 62 deletions(-) create mode 100644 tool/grind/generate_deprecations.dart diff --git a/.pubignore b/.pubignore index 2fbab300a..08992ce75 100644 --- a/.pubignore +++ b/.pubignore @@ -1,5 +1,5 @@ # This should be identical to .gitignore except that it doesn't exclude -# generated protobuf files. +# generated Dart files. .buildlog .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d64f38c1..d06b712fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.77.3 + +### Dart API + +* `Deprecation.duplicateVariableFlags` has been deprecated and replaced with + `Deprecation.duplicateVarFlags` to make it consistent with the + `duplicate-var-flags` name used on the command line and in the JS API. + ## 1.77.2 * Don't emit deprecation warnings for functions and mixins beginning with `__`. diff --git a/lib/src/deprecation.dart b/lib/src/deprecation.dart index b13180e10..cec976714 100644 --- a/lib/src/deprecation.dart +++ b/lib/src/deprecation.dart @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC. Use of this source code is governed by an +// Copyright 2024 Google LLC. 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. @@ -10,34 +10,42 @@ import 'util/nullable.dart'; /// A deprecated feature in the language. enum Deprecation { - /// Deprecation for passing a string to `call` instead of `get-function`. + // START AUTOGENERATED CODE + // + // DO NOT EDIT. This section was generated from the language repo. + // See tool/grind/generate_deprecations.dart for details. + // + // Checksum: 22d9bdbe92eb39b3c0d6d64ebe1879a431c0037e + + /// Deprecation for passing a string directly to meta.call(). callString('call-string', deprecatedIn: '0.0.0', description: 'Passing a string directly to meta.call().'), - /// Deprecation for `@elseif`. + /// Deprecation for @elseif. elseif('elseif', deprecatedIn: '1.3.2', description: '@elseif.'), - /// Deprecation for parsing `@-moz-document`. + /// Deprecation for @-moz-document. mozDocument('moz-document', deprecatedIn: '1.7.2', description: '@-moz-document.'), - /// Deprecation for importers using relative canonical URLs. - relativeCanonical('relative-canonical', deprecatedIn: '1.14.2'), + /// Deprecation for imports using relative canonical URLs. + relativeCanonical('relative-canonical', + deprecatedIn: '1.14.2', + description: 'Imports using relative canonical URLs.'), - /// Deprecation for declaring new variables with `!global`. + /// Deprecation for declaring new variables with !global. newGlobal('new-global', deprecatedIn: '1.17.2', description: 'Declaring new variables with !global.'), - /// Deprecation for certain functions in the color module matching the - /// behavior of their global counterparts for compatiblity reasons. + /// Deprecation for using color module functions in place of plain CSS functions. colorModuleCompat('color-module-compat', deprecatedIn: '1.23.0', description: 'Using color module functions in place of plain CSS functions.'), - /// Deprecation for treating `/` as division. + /// Deprecation for / operator for division. slashDiv('slash-div', deprecatedIn: '1.33.0', description: '/ operator for division.'), @@ -46,46 +54,55 @@ enum Deprecation { deprecatedIn: '1.54.0', description: 'Leading, trailing, and repeated combinators.'), - /// Deprecation for ambiguous `+` and `-` operators. + /// Deprecation for ambiguous + and - operators. strictUnary('strict-unary', deprecatedIn: '1.55.0', description: 'Ambiguous + and - operators.'), - /// Deprecation for passing invalid units to certain built-in functions. + /// Deprecation for passing invalid units to built-in functions. functionUnits('function-units', deprecatedIn: '1.56.0', description: 'Passing invalid units to built-in functions.'), - /// Deprecation for passing percentages to the Sass abs() function. - absPercent('abs-percent', - deprecatedIn: '1.65.0', - description: 'Passing percentages to the Sass abs() function.'), - - duplicateVariableFlags('duplicate-var-flags', + /// Deprecation for using !default or !global multiple times for one variable. + duplicateVarFlags('duplicate-var-flags', deprecatedIn: '1.62.0', description: 'Using !default or !global multiple times for one variable.'), + /// Deprecation for passing null as alpha in the ${isJS ? 'JS': 'Dart'} API. nullAlpha('null-alpha', deprecatedIn: '1.62.3', description: 'Passing null as alpha in the ${isJS ? 'JS' : 'Dart'} API.'), + /// Deprecation for passing percentages to the Sass abs() function. + absPercent('abs-percent', + deprecatedIn: '1.65.0', + description: 'Passing percentages to the Sass abs() function.'), + + /// Deprecation for using the current working directory as an implicit load path. fsImporterCwd('fs-importer-cwd', deprecatedIn: '1.73.0', description: 'Using the current working directory as an implicit load path.'), + /// Deprecation for function and mixin names beginning with --. cssFunctionMixin('css-function-mixin', deprecatedIn: '1.76.0', description: 'Function and mixin names beginning with --.'), - @Deprecated('This deprecation name was never actually used.') - calcInterp('calc-interp', deprecatedIn: null), - - /// Deprecation for `@import` rules. + /// Deprecation for @import rules. import.future('import', description: '@import rules.'), + // END AUTOGENERATED CODE + /// Used for deprecations coming from user-authored code. - userAuthored('user-authored', deprecatedIn: null); + userAuthored('user-authored', deprecatedIn: null), + + @Deprecated('This deprecation name was never actually used.') + calcInterp('calc-interp', deprecatedIn: null); + + @Deprecated('Use duplicateVarFlags instead.') + static const duplicateVariableFlags = duplicateVarFlags; /// A unique ID for this deprecation in kebab case. /// diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index e72e2527b..e372e3912 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -235,7 +235,7 @@ abstract class StylesheetParser extends Parser { case 'default': if (guarded) { logger.warnForDeprecation( - Deprecation.duplicateVariableFlags, + Deprecation.duplicateVarFlags, '!default should only be written once for each variable.\n' 'This will be an error in Dart Sass 2.0.0.', span: scanner.spanFrom(flagStart)); @@ -248,7 +248,7 @@ abstract class StylesheetParser extends Parser { scanner.spanFrom(flagStart)); } else if (global) { logger.warnForDeprecation( - Deprecation.duplicateVariableFlags, + Deprecation.duplicateVarFlags, '!global should only be written once for each variable.\n' 'This will be an error in Dart Sass 2.0.0.', span: scanner.spanFrom(flagStart)); diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index c0839df75..e82b07c57 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,7 @@ +## 10.4.3 + +* No user-visible changes. + ## 10.4.2 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index e11f303a5..0943de732 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.2 +version: 10.4.3 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.2 + sass: 1.77.3 dev_dependencies: dartdoc: ">=6.0.0 <9.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index c4acb1234..5260eba17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.77.2 +version: 1.77.3 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass diff --git a/test/double_check_test.dart b/test/double_check_test.dart index 1da209885..44def85ae 100644 --- a/test/double_check_test.dart +++ b/test/double_check_test.dart @@ -7,25 +7,39 @@ import 'dart:io'; import 'dart:convert'; +import 'package:crypto/crypto.dart'; import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:test/test.dart'; +import '../tool/grind/generate_deprecations.dart' as deprecations; import '../tool/grind/synchronize.dart' as synchronize; /// Tests that double-check that everything in the repo looks sensible. void main() { - group("synchronized file is up-to-date:", () { - synchronize.sources.forEach((sourcePath, targetPath) { - test(targetPath, () { - if (File(targetPath).readAsStringSync() != - synchronize.synchronizeFile(sourcePath)) { - fail("$targetPath is out-of-date.\n" - "Run `dart pub run grinder` to update it."); - } + group("up-to-date generated", () { + group("synchronized file:", () { + synchronize.sources.forEach((sourcePath, targetPath) { + test(targetPath, () { + if (File(targetPath).readAsStringSync() != + synchronize.synchronizeFile(sourcePath)) { + fail("$targetPath is out-of-date.\n" + "Run `dart run grinder` to update it."); + } + }); }); }); + + test("deprecations", () { + var inputText = File(deprecations.yamlPath).readAsStringSync(); + var outputText = File(deprecations.dartPath).readAsStringSync(); + var checksum = sha1.convert(utf8.encode(inputText)); + if (!outputText.contains('// Checksum: $checksum')) { + fail('${deprecations.dartPath} is out-of-date.\n' + 'Run `dart run grinder` to update it.'); + } + }); }, // Windows sees different bytes than other OSes, possibly because of // newline normalization issues. diff --git a/tool/grind.dart b/tool/grind.dart index 631bdebdf..7af45b438 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -11,6 +11,7 @@ import 'package:grinder/grinder.dart'; import 'package:path/path.dart' as p; import 'package:source_span/source_span.dart'; +import 'grind/generate_deprecations.dart'; import 'grind/synchronize.dart'; import 'grind/utils.dart'; @@ -18,8 +19,10 @@ export 'grind/bazel.dart'; export 'grind/benchmark.dart'; export 'grind/double_check.dart'; export 'grind/frameworks.dart'; +export 'grind/generate_deprecations.dart'; export 'grind/subpackages.dart'; export 'grind/synchronize.dart'; +export 'grind/utils.dart'; void main(List args) { pkg.humanName.value = "Dart Sass"; @@ -127,7 +130,7 @@ void main(List args) { } @DefaultTask('Compile async code and reformat.') -@Depends(format, synchronize) +@Depends(format, synchronize, deprecations) void all() {} @Task('Run the Dart formatter.') @@ -140,7 +143,7 @@ void npmInstall() => run(Platform.isWindows ? "npm.cmd" : "npm", arguments: ["install"]); @Task('Runs the tasks that are required for running tests.') -@Depends(format, synchronize, protobuf, "pkg-npm-dev", npmInstall, +@Depends(format, synchronize, protobuf, deprecations, "pkg-npm-dev", npmInstall, "pkg-standalone-dev") void beforeTest() {} @@ -213,9 +216,9 @@ String _readAndResolveMarkdown(String path) => File(path) /// Returns a map from JS type declaration file names to their contnets. Map _fetchJSTypes() { - var languageRepo = _updateLanguageRepo(); + updateLanguageRepo(); - var typeRoot = p.join(languageRepo, 'js-api-doc'); + var typeRoot = p.join('build/language', 'js-api-doc'); return { for (var entry in Directory(typeRoot).listSync(recursive: true)) if (entry is File && entry.path.endsWith('.d.ts')) @@ -231,6 +234,7 @@ void _matchError(Match match, String message, {Object? url}) { } @Task('Compile the protocol buffer definition to a Dart library.') +@Depends(updateLanguageRepo) Future protobuf() async { Directory('build').createSync(recursive: true); @@ -250,8 +254,6 @@ dart run protoc_plugin "\$@" run('chmod', arguments: ['a+x', 'build/protoc-gen-dart']); } - _updateLanguageRepo(); - await runAsync("buf", arguments: ["generate"], runOptions: RunOptions(environment: { @@ -321,19 +323,3 @@ String _updateHomebrewLanguageRevision(String formula) { match.group(0)!.replaceFirst(match.group(1)!, languageRepoRevision) + formula.substring(match.end); } - -/// Clones the main branch of `github.com/sass/sass` and returns the path to the -/// clone. -/// -/// If the `UPDATE_SASS_SASS_REPO` environment variable is `false`, this instead -/// assumes the repo that already exists at `build/language/sass`. -/// `UPDATE_SASS_PROTOCOL` is also checked as a deprecated alias for -/// `UPDATE_SASS_SASS_REPO`. -String _updateLanguageRepo() => - // UPDATE_SASS_PROTOCOL is considered deprecated, because it doesn't apply as - // generically to other tasks. - Platform.environment['UPDATE_SASS_SASS_REPO'] != 'false' && - Platform.environment['UPDATE_SASS_PROTOCOL'] != 'false' - ? cloneOrCheckout("https://github.com/sass/sass.git", "main", - name: 'language') - : 'build/language'; diff --git a/tool/grind/double_check.dart b/tool/grind/double_check.dart index 4ec0cd8e7..8b1dca18f 100644 --- a/tool/grind/double_check.dart +++ b/tool/grind/double_check.dart @@ -5,13 +5,12 @@ import 'dart:io'; import 'package:cli_pkg/cli_pkg.dart' as pkg; +import 'package:collection/collection.dart'; import 'package:grinder/grinder.dart'; import 'package:path/path.dart' as p; import 'package:pub_api_client/pub_api_client.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; -import 'package:sass/src/utils.dart'; - import 'utils.dart'; @Task('Verify that the package is in a good state to release.') @@ -21,7 +20,7 @@ Future doubleCheckBeforeRelease() async { fail("GITHUB_REF $ref is different than pubspec version ${pkg.version}."); } - if (listEquals(pkg.version.preRelease, ["dev"])) { + if (const ListEquality().equals(pkg.version.preRelease, ["dev"])) { fail("${pkg.version} is a dev release."); } diff --git a/tool/grind/generate_deprecations.dart b/tool/grind/generate_deprecations.dart new file mode 100644 index 000000000..21ce6f58e --- /dev/null +++ b/tool/grind/generate_deprecations.dart @@ -0,0 +1,82 @@ +// Copyright 2024 Google LLC. 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 'dart:convert'; +import 'dart:io'; + +import 'package:crypto/crypto.dart'; +import 'package:dart_style/dart_style.dart'; +import 'package:grinder/grinder.dart'; +import 'package:yaml/yaml.dart'; + +import 'utils.dart'; + +const yamlPath = 'build/language/spec/deprecations.yaml'; +const dartPath = 'lib/src/deprecation.dart'; + +final _blockRegex = + RegExp(r'// START AUTOGENERATED CODE[\s\S]*?// END AUTOGENERATED CODE'); + +@Task('Generate deprecation.g.dart from the list in the language repo.') +@Depends(updateLanguageRepo) +void deprecations() { + var yamlFile = File(yamlPath); + var dartFile = File(dartPath); + var yamlText = yamlFile.readAsStringSync(); + var data = loadYaml(yamlText, sourceUrl: yamlFile.uri) as Map; + var dartText = dartFile.readAsStringSync(); + var buffer = StringBuffer('''// START AUTOGENERATED CODE + // + // DO NOT EDIT. This section was generated from the language repo. + // See tool/grind/generate_deprecations.dart for details. + // + // Checksum: ${sha1.convert(utf8.encode(yamlText))} + +'''); + for (var MapEntry(:String key, :value) in data.entries) { + var camelCase = key.replaceAllMapped( + RegExp(r'-(.)'), (match) => match.group(1)!.toUpperCase()); + var (description, deprecatedIn, obsoleteIn) = switch (value) { + { + 'description': String description, + 'dart-sass': {'status': 'future'}, + } => + (description, null, null), + { + 'description': String description, + 'dart-sass': {'status': 'active', 'deprecated': String deprecatedIn}, + } => + (description, deprecatedIn, null), + { + 'description': String description, + 'dart-sass': { + 'status': 'obsolete', + 'deprecated': String deprecatedIn, + 'obsolete': String obsoleteIn + }, + } => + (description, deprecatedIn, obsoleteIn), + _ => throw Exception('Invalid deprecation $key: $value') + }; + description = + description.replaceAll(r'$PLATFORM', r"${isJS ? 'JS': 'Dart'}"); + var constructorName = deprecatedIn == null ? '.future' : ''; + var deprecatedClause = + deprecatedIn == null ? '' : "deprecatedIn: '$deprecatedIn', "; + var obsoleteClause = + obsoleteIn == null ? '' : "obsoleteIn: '$obsoleteIn', "; + var comment = 'Deprecation for ${description.substring(0, 1).toLowerCase()}' + '${description.substring(1)}'; + buffer.writeln('/// $comment'); + buffer.writeln( + "$camelCase$constructorName('$key', $deprecatedClause$obsoleteClause" + "description: '$description'),"); + } + buffer.write('\n // END AUTOGENERATED CODE'); + if (!dartText.contains(_blockRegex)) { + fail("Couldn't find block for generated code in lib/src/deprecation.dart"); + } + var newCode = dartText.replaceFirst(_blockRegex, buffer.toString()); + dartFile.writeAsStringSync(DartFormatter().format(newCode)); +} diff --git a/tool/grind/utils.dart b/tool/grind/utils.dart index 21d9f66c8..86ea7ed96 100644 --- a/tool/grind/utils.dart +++ b/tool/grind/utils.dart @@ -100,3 +100,20 @@ void afterTask(String taskName, FutureOr callback()) { await callback(); }); } + +/// Clones the main branch of `github.com/sass/sass`. +/// +/// If the `UPDATE_SASS_SASS_REPO` environment variable is `false`, this instead +/// assumes the repo that already exists at `build/language/sass`. +/// `UPDATE_SASS_PROTOCOL` is also checked as a deprecated alias for +/// `UPDATE_SASS_SASS_REPO`. +@Task('Clones the main branch of `github.com/sass/sass` if necessary.') +void updateLanguageRepo() { + // UPDATE_SASS_PROTOCOL is considered deprecated, because it doesn't apply as + // generically to other tasks. + if (Platform.environment['UPDATE_SASS_SASS_REPO'] != 'false' && + Platform.environment['UPDATE_SASS_PROTOCOL'] != 'false') { + cloneOrCheckout("https://github.com/sass/sass.git", "main", + name: 'language'); + } +}