From 4121f4dc438e087dce7d91e83af0ef5934dca0a8 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Mon, 9 Feb 2026 15:43:56 -0700 Subject: [PATCH 1/6] Vendor transformer_utils to ease analyzer upgrades --- .../builder/codegen/accessors_generator.dart | 2 +- .../codegen/component_factory_generator.dart | 2 +- .../codegen/typed_map_impl_generator.dart | 2 +- lib/src/builder/parsing/ast_util.dart | 2 +- lib/src/builder/parsing/meta.dart | 2 +- .../src/analyzer_helpers.dart | 257 ++++++++++++ .../transformer_utils/src/barback_utils.dart | 46 +++ .../transformer_utils/src/node_with_meta.dart | 71 ++++ .../transformer_utils/src/text_util.dart | 153 +++++++ .../src/transformed_source_file.dart | 167 ++++++++ .../transformer_utils/transformer_utils.dart | 26 ++ pubspec.yaml | 1 - .../vendor/transformer_utils/test_utils.dart | 66 +++ .../unit/analyzer_helpers_test.dart | 386 ++++++++++++++++++ .../unit/barback_utils_test.dart | 84 ++++ .../unit/node_with_meta_test.dart | 77 ++++ .../unit/text_util_test.dart | 125 ++++++ .../unit/transformed_source_file_test.dart | 243 +++++++++++ 18 files changed, 1706 insertions(+), 6 deletions(-) create mode 100644 lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart create mode 100644 lib/src/builder/vendor/transformer_utils/src/barback_utils.dart create mode 100644 lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart create mode 100644 lib/src/builder/vendor/transformer_utils/src/text_util.dart create mode 100644 lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart create mode 100644 lib/src/builder/vendor/transformer_utils/transformer_utils.dart create mode 100644 test/vm_tests/builder/vendor/transformer_utils/test_utils.dart create mode 100644 test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart create mode 100644 test/vm_tests/builder/vendor/transformer_utils/unit/barback_utils_test.dart create mode 100644 test/vm_tests/builder/vendor/transformer_utils/unit/node_with_meta_test.dart create mode 100644 test/vm_tests/builder/vendor/transformer_utils/unit/text_util_test.dart create mode 100644 test/vm_tests/builder/vendor/transformer_utils/unit/transformed_source_file_test.dart diff --git a/lib/src/builder/codegen/accessors_generator.dart b/lib/src/builder/codegen/accessors_generator.dart index 2951cc3f7..d5e6696b8 100644 --- a/lib/src/builder/codegen/accessors_generator.dart +++ b/lib/src/builder/codegen/accessors_generator.dart @@ -14,7 +14,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:over_react/src/component_declaration/annotations.dart' as annotations; -import 'package:transformer_utils/transformer_utils.dart'; +import 'package:over_react/src/builder/vendor/transformer_utils/transformer_utils.dart'; import '../parsing.dart'; import '../util.dart'; diff --git a/lib/src/builder/codegen/component_factory_generator.dart b/lib/src/builder/codegen/component_factory_generator.dart index 0fd9223af..1fc05dfbb 100644 --- a/lib/src/builder/codegen/component_factory_generator.dart +++ b/lib/src/builder/codegen/component_factory_generator.dart @@ -14,7 +14,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:over_react/src/component_declaration/annotations.dart' as annotations; -import 'package:transformer_utils/transformer_utils.dart'; +import 'package:over_react/src/builder/vendor/transformer_utils/transformer_utils.dart'; import '../parsing.dart'; import '../util.dart'; diff --git a/lib/src/builder/codegen/typed_map_impl_generator.dart b/lib/src/builder/codegen/typed_map_impl_generator.dart index 887c75e67..728c07eb0 100644 --- a/lib/src/builder/codegen/typed_map_impl_generator.dart +++ b/lib/src/builder/codegen/typed_map_impl_generator.dart @@ -13,7 +13,7 @@ // limitations under the License. import 'package:analyzer/dart/ast/ast.dart'; -import 'package:transformer_utils/transformer_utils.dart'; +import 'package:over_react/src/builder/vendor/transformer_utils/transformer_utils.dart'; import '../parsing.dart'; import '../util.dart'; diff --git a/lib/src/builder/parsing/ast_util.dart b/lib/src/builder/parsing/ast_util.dart index 2294bb273..5fc5f8e11 100644 --- a/lib/src/builder/parsing/ast_util.dart +++ b/lib/src/builder/parsing/ast_util.dart @@ -18,7 +18,7 @@ import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:over_react/src/builder/codegen/names.dart'; import 'package:source_span/source_span.dart'; -import 'package:transformer_utils/transformer_utils.dart'; +import 'package:over_react/src/builder/vendor/transformer_utils/transformer_utils.dart'; import 'ast_util/classish.dart'; import 'util.dart'; diff --git a/lib/src/builder/parsing/meta.dart b/lib/src/builder/parsing/meta.dart index f4ea2c527..9f9b44345 100644 --- a/lib/src/builder/parsing/meta.dart +++ b/lib/src/builder/parsing/meta.dart @@ -17,7 +17,7 @@ import 'dart:mirrors' as mirrors; import 'package:analyzer/dart/ast/ast.dart'; import 'package:build/build.dart' show log; import 'package:collection/collection.dart' show IterableExtension; -import 'package:transformer_utils/transformer_utils.dart'; +import 'package:over_react/src/builder/vendor/transformer_utils/transformer_utils.dart'; import 'ast_util.dart'; diff --git a/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart b/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart new file mode 100644 index 000000000..47409b022 --- /dev/null +++ b/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart @@ -0,0 +1,257 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/lib/ + +library; + +import 'dart:mirrors' as mirrors; + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:collection/collection.dart' show IterableExtension; + +/// Returns a copy of a class [member] declaration with [body] as a new +/// implementation. +/// +/// Currently only supports: +/// * [FieldDeclaration] (single variable only) +/// * [MethodDeclaration] (getter, setter, and methods) +String copyClassMember(ClassMember? member, String body) { + if (member is FieldDeclaration) return _copyFieldDeclaration(member, body); + if (member is MethodDeclaration) { + if (member.isGetter) return _copyGetterDeclaration(member, body); + if (member.isSetter) return _copySetterDeclaration(member, body); + return _copyMethodDeclaration(member, body); + } + throw UnsupportedError( + 'Unsupported class member type: ${member.runtimeType}. ' + 'Only FieldDeclaration and MethodDeclaration are supported.'); +} + +/// Finds and returns all declarations within a compilation [unit] that are +/// annotated with the given [annotation] class. +/// +/// If this is being leveraged within a transformer, you can associate the +/// returned [DeclarationWithMeta] instance with the asset in which it is +/// located by passing in an [assetId]. +Iterable getDeclarationsAnnotatedBy( + CompilationUnit unit, Type annotation) { + var annotationName = _getReflectedName(annotation); + return unit.declarations.where((member) { + return member.metadata.any((meta) => meta.name.name == annotationName); + }); +} + +/// Returns the value of the specified [expression] AST node if it represents a literal. +/// +/// For non-literal nodes, the return value will be the result of calling [onUnsupportedExpression] with [expression]. +/// +/// If [onUnsupportedExpression] isn't specified, an [UnsupportedError] will be thrown. +/// +/// Currently only supports: +/// * [StringLiteral] +/// * [BooleanLiteral] +/// * [IntegerLiteral] +/// * [NullLiteral] +dynamic getValue(Expression expression, + {dynamic onUnsupportedExpression(Expression expression)?}) { + if (expression is StringLiteral) { + var value = expression.stringValue; + if (value != null) { + return value; + } + } else if (expression is BooleanLiteral) { + return expression.value; + } else if (expression is IntegerLiteral) { + return expression.value; + } else if (expression is NullLiteral) { + return null; + } + + if (onUnsupportedExpression != null) { + return onUnsupportedExpression(expression); + } + + throw UnsupportedError('Unsupported expression: $expression. ' + 'Must be a uninterpolated string, boolean, integer, or null literal.'); +} + +/// Returns the first annotation AST node on [member] of type [annotationType], +/// or null if no matching annotations are found. +Annotation? getMatchingAnnotation(AnnotatedNode member, Type annotationType) { + // Be sure to use `originalDeclaration` so that generic parameters work. + final classMirror = mirrors.reflectClass(annotationType).originalDeclaration + as mirrors.ClassMirror; + String className = mirrors.MirrorSystem.getName(classMirror.simpleName); + + // Find the annotation that matches [type]'s name. + return member.metadata.firstWhereOrNull((annotation) { + return _getClassName(annotation) == className; + }); +} + +/// Uses reflection to instantiate and returns the first annotation on [member] of type +/// [annotationType], or null if no matching annotations are found. +/// +/// Annotation constructors are currently limited to the values supported by [getValue]. +/// +/// Naively assumes that the name of the [annotationType] class is canonical. +dynamic instantiateAnnotation(AnnotatedNode member, Type annotationType, + {dynamic onUnsupportedArgument(Expression argument)?}) { + final matchingAnnotation = getMatchingAnnotation(member, annotationType); + + // If no annotation is found, return null. + if (matchingAnnotation == null) { + return null; + } + + final matchingAnnotationArgs = matchingAnnotation.arguments; + if (matchingAnnotationArgs == null) { + throw 'Annotation not invocation of constructor: `$matchingAnnotation`. ' + 'This is likely due to invalid usage of the annotation class, but could' + 'also be a name conflict with the specified type `$annotationType`'; + } + + // Get the parameters from the annotation's AST. + Map namedParameters = {}; + List positionalParameters = []; + + matchingAnnotationArgs.arguments.forEach((argument) { + var onUnsupportedExpression = onUnsupportedArgument == null + ? null + : (_) => onUnsupportedArgument(argument); + + if (argument is NamedExpression) { + var name = argument.name.label.name; + var value = getValue(argument.expression, + onUnsupportedExpression: onUnsupportedExpression); + + namedParameters[Symbol(name)] = value; + } else { + var value = + getValue(argument, onUnsupportedExpression: onUnsupportedExpression); + + positionalParameters.add(value); + } + }); + + // Instantiate and return an instance of the annotation using reflection. + String constructorName = _getConstructorName(matchingAnnotation) ?? ''; + + // Be sure to use `originalDeclaration` so that generic parameters work. + final classMirror = mirrors.reflectClass(annotationType).originalDeclaration + as mirrors.ClassMirror; + + try { + var instanceMirror = classMirror.newInstance( + Symbol(constructorName), positionalParameters, namedParameters); + return instanceMirror.reflectee; + } catch (e, stacktrace) { + throw 'Unable to instantiate annotation: $matchingAnnotation. This is ' + 'likely due to improper usage, or a naming conflict with ' + 'annotationType $annotationType. Original error: $e. Stacktrace: $stacktrace'; + } +} + +String _copyFieldDeclaration(FieldDeclaration decl, String initializer) { + var result = ''; + if (decl.fields.type != null) { + result = '${decl.fields.type}'; + } else if (decl.staticKeyword == null) { + result = 'var'; + } + if (decl.staticKeyword != null) { + result = '${decl.staticKeyword} $result'; + } + result = '$result ${decl.fields.variables.first.name.lexeme}'; + if (initializer.isNotEmpty) { + result = '$result = $initializer;'; + } else { + result = '$result;'; + } + return result; +} + +String _copyGetterDeclaration(MethodDeclaration decl, String body) { + var result = ''; + if (decl.returnType != null) { + result = '${decl.returnType} get'; + } else { + result = 'get'; + } + if (decl.isStatic) { + result = 'static $result'; + } + + result = '$result ${decl.name.lexeme}'; + if (decl.body.keyword != null) { + result = '$result ${decl.body.keyword}${decl.body.star ?? ''}'; + } + result = '$result {\n$body\n}'; + return result; +} + +String _copySetterDeclaration(MethodDeclaration decl, String body) { + var result = 'void set'; + if (decl.isStatic) { + result = 'static $result'; + } + result = '$result ${decl.name.lexeme}${decl.parameters} {\n$body\n }'; + return result; +} + +String _copyMethodDeclaration(MethodDeclaration decl, String body) { + var result = '${decl.name.lexeme}'; + if (decl.returnType != null) { + result = '${decl.returnType} $result'; + } + if (decl.isStatic) { + result = 'static $result'; + } + result = '$result${decl.parameters}'; + if (decl.body.keyword != null) { + result = '$result ${decl.body.keyword}${decl.body.star ?? ''}'; + } + result = '$result {\n$body\n }'; + return result; +} + +/// Returns the name of the class being instantiated for [annotation], or null +/// if the annotation is not the invocation of a constructor. +/// +/// Workaround for a Dart analyzer issue where the constructor name is included +/// in [annotation.name]. +String _getClassName(Annotation annotation) => + annotation.name.name.split('.').first; + +/// Returns the name of the constructor being instantiated for [annotation], or +/// null if the annotation is not the invocation of a named constructor. +/// +/// Workaround for a Dart analyzer issue where the constructor name is included +/// in [annotation.name]. +String? _getConstructorName(Annotation annotation) { + var constructorName = annotation.constructorName?.name; + if (constructorName == null) { + var periodIndex = annotation.name.name.indexOf('.'); + if (periodIndex != -1) { + constructorName = annotation.name.name.substring(periodIndex + 1); + } + } + + return constructorName; +} + +/// Get the name of a [type] via reflection. +String _getReflectedName(Type type) => + mirrors.MirrorSystem.getName(mirrors.reflectType(type).simpleName); diff --git a/lib/src/builder/vendor/transformer_utils/src/barback_utils.dart b/lib/src/builder/vendor/transformer_utils/src/barback_utils.dart new file mode 100644 index 000000000..6b0d167b3 --- /dev/null +++ b/lib/src/builder/vendor/transformer_utils/src/barback_utils.dart @@ -0,0 +1,46 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/lib/ + +library; + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:build/build.dart'; +import 'package:path/path.dart' as path; +import 'package:source_span/source_span.dart'; + +/// Converts [id] to a "package:" URI. +/// +/// This will return a schemeless URI if [id] doesn't represent a library in +/// `lib/`. +Uri assetIdToPackageUri(AssetId id) { + if (!id.path.startsWith('lib/')) return Uri(path: id.path); + return Uri( + scheme: 'package', + path: path.url.join(id.package, id.path.replaceFirst('lib/', ''))); +} + +/// Returns a [SourceSpan] spanning from the beginning to the end of the given +/// [node]. The preceding comment and metadata will be excluded if +/// [skipCommentAndMetadata] is true. +SourceSpan getSpanForNode(SourceFile sourceFile, AstNode node, + {bool skipCommentAndMetadata = true}) { + if (skipCommentAndMetadata && node is AnnotatedNode) { + return sourceFile.span( + node.firstTokenAfterCommentAndMetadata.offset, node.end); + } + + return sourceFile.span(node.offset, node.end); +} diff --git a/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart b/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart new file mode 100644 index 000000000..b2c68a5d7 --- /dev/null +++ b/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart @@ -0,0 +1,71 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/lib/ + +library; + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:build/build.dart'; + +import './analyzer_helpers.dart'; + +/// Utility class that allows for easy access to an annotated node's +/// instantiated annotation. +class NodeWithMeta { + /// The optionally-annotated node. + final TNode node; + + /// The optional asset ID associated with this node. + final AssetId? assetId; + + /// The node of the [TMeta] annotation, if it exists. + final Annotation? metaNode; + + /// A reflectively-instantiated version of [metaNode], if it exists. + TMeta? _meta; + + /// The arguments passed to the metadata that are not supported by [getValue], + /// (or by special handling in subclasses) and therefore not represented in the instantiation of [meta]. + final List unsupportedArguments = []; + + /// Construct a [NodeWithMeta] instance from an [AnnotatedNode]. + /// The original node will be available via [node]. + /// The instantiated annotation of type `TMeta` will be available via [meta]. + NodeWithMeta(TNode node, {this.assetId}) + : this.node = node, + this.metaNode = getMatchingAnnotation(node, TMeta) { + this._meta = instantiateAnnotation(node, TMeta, + onUnsupportedArgument: unsupportedArguments.add); + } + + /// Whether this node's metadata has arguments that could not be initialized using [getValue] + /// (or by special handling in subclasses), and therefore cannot represented in the instantiation of [meta]. + bool get isIncomplete => unsupportedArguments.isNotEmpty; + + /// A reflectively-instantiated version of [metaNode], if it exists. + /// + /// Throws a [StateError] if this node's metadata is incomplete. + TMeta? get meta { + if (isIncomplete) { + throw StateError( + 'Metadata is incomplete; unsupported arguments $unsupportedArguments. ' + 'Use `potentiallyIncompleteMeta` instead.'); + } + return _meta; + } + + /// A reflectively-instantiated version of [metaNode], if it exists. + TMeta? get potentiallyIncompleteMeta => _meta; +} diff --git a/lib/src/builder/vendor/transformer_utils/src/text_util.dart b/lib/src/builder/vendor/transformer_utils/src/text_util.dart new file mode 100644 index 000000000..a60a9c8dc --- /dev/null +++ b/lib/src/builder/vendor/transformer_utils/src/text_util.dart @@ -0,0 +1,153 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/lib/ + +library; + +/// Returns [content] escaped and optionally quoted for use as a string literal +/// in Dart source code. +/// +/// If [content] is null, a string version of the the `null` literal will be +/// returned instead. +String stringLiteral(String? content, + {bool quote = true, bool useSingleQuote = true}) { + // Adapted from dart.convert library's JSON encoder: + // https://github.com/dart-lang/sdk/blob/1.12.0/sdk/lib/convert/json.dart#L565 + // + // + // Copyright 2012, the Dart project authors. All rights reserved. + // Redistribution and use in source and binary forms, with or without + // modification, are permitted provided that the following conditions are + // met: + // * Redistributions of source code must retain the above copyright + // notice, this list of conditions and the following disclaimer. + // * Redistributions in binary form must reproduce the above + // copyright notice, this list of conditions and the following + // disclaimer in the documentation and/or other materials provided + // with the distribution. + // * Neither the name of Google Inc. nor the names of its + // contributors may be used to endorse or promote products derived + // from this software without specific prior written permission. + // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + if (content == null) { + return 'null'; + } + + const int BACKSPACE = 0x08; + const int TAB = 0x09; + const int NEWLINE = 0x0a; + const int CARRIAGE_RETURN = 0x0d; + const int FORM_FEED = 0x0c; + const int DOLLAR_SIGN = 0x24; + const int SINGLE_QUOTE = 0x27; + const int DOUBLE_QUOTE = 0x22; + const int CHAR_0 = 0x30; + const int BACKSLASH = 0x5c; + const int CHAR_b = 0x62; + const int CHAR_f = 0x66; + const int CHAR_n = 0x6e; + const int CHAR_r = 0x72; + const int CHAR_t = 0x74; + const int CHAR_u = 0x75; + + int quoteChar = useSingleQuote ? SINGLE_QUOTE : DOUBLE_QUOTE; + + var buffer = StringBuffer(); + + void writeStringSlice(String string, int start, int end) { + buffer.write(string.substring(start, end)); + } + + void writeCharCode(int charCode) { + buffer.writeCharCode(charCode); + } + + void writeString(String string) { + buffer.write(string); + } + + // ('0' + x) or ('a' + x - 10) + int hexDigit(int x) => x < 10 ? 48 + x : 87 + x; + + if (quote) { + writeCharCode(quoteChar); + } + + int offset = 0; + final int length = content.length; + for (int i = 0; i < length; i++) { + int charCode = content.codeUnitAt(i); + if (charCode > BACKSLASH) continue; + if (charCode < 32) { + if (i > offset) writeStringSlice(content, offset, i); + offset = i + 1; + writeCharCode(BACKSLASH); + switch (charCode) { + case BACKSPACE: + writeCharCode(CHAR_b); + break; + case TAB: + writeCharCode(CHAR_t); + break; + case NEWLINE: + writeCharCode(CHAR_n); + break; + case FORM_FEED: + writeCharCode(CHAR_f); + break; + case CARRIAGE_RETURN: + writeCharCode(CHAR_r); + break; + default: + writeCharCode(CHAR_u); + writeCharCode(CHAR_0); + writeCharCode(CHAR_0); + writeCharCode(hexDigit((charCode >> 4) & 0xf)); + writeCharCode(hexDigit(charCode & 0xf)); + break; + } + } else if (charCode == quoteChar || + charCode == BACKSLASH || + charCode == DOLLAR_SIGN) { + if (i > offset) writeStringSlice(content, offset, i); + offset = i + 1; + writeCharCode(BACKSLASH); + writeCharCode(charCode); + } + } + + if (offset == 0) { + writeString(content); + } else if (offset < length) { + writeStringSlice(content, offset, length); + } + + if (quote) { + writeCharCode(quoteChar); + } + + return buffer.toString(); +} diff --git a/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart b/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart new file mode 100644 index 000000000..398545404 --- /dev/null +++ b/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart @@ -0,0 +1,167 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/lib/ + +library; + +import 'dart:convert'; + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:source_span/source_span.dart'; + +/// A record used internally by [TransformedSourceFile] that represents the +/// replacement of a [SourceSpan] with a string of text. +class _Replacement { + final SourceSpan span; + final String newText; + + _Replacement(this.span, this.newText); + + @override + String toString() => 'Replacement (span: $span, newText: $newText)'; +} + +/// A utility that allows for modification of a [SourceFile] via a series of +/// replacements, insertions, and removals. +class TransformedSourceFile { + final List<_Replacement> _replacements = []; + + final SourceFile sourceFile; + + TransformedSourceFile(this.sourceFile); + + bool get isModified => _replacements.isNotEmpty; + + void replace(SourceSpan span, String text) { + _replacements.add(_Replacement(span, text)); + } + + void insert(SourceLocation location, String text) { + _replacements.add(_Replacement(location.pointSpan(), text)); + } + + void remove(SourceSpan span, {bool preserveNewlines = false}) { + String replacement; + if (preserveNewlines) { + replacement = '\n' * '\n'.allMatches(span.text).length; + } else { + replacement = ''; + } + + _replacements.add(_Replacement(span, replacement)); + } + + void iterateReplacements( + {onUnmodified(String string)?, + onRemoval(String string)?, + onAddition(String string)?}) { + _replacements.sort((r1, r2) => r1.span.compareTo(r2.span)); + + var lastEdge = 0; + for (_Replacement replacement in _replacements) { + if (replacement.span.start.offset < lastEdge) { + throw 'Overlapping replacement $replacement in replacements $_replacements.'; + } + + var unmodifiedText = + sourceFile.getText(lastEdge, replacement.span.start.offset); + var removalText = replacement.span.text; + var additionText = replacement.newText; + + if (onUnmodified != null && unmodifiedText.isNotEmpty) { + onUnmodified(unmodifiedText); + } + if (onRemoval != null && removalText.isNotEmpty) { + onRemoval(removalText); + } + if (onAddition != null && additionText.isNotEmpty) { + onAddition(additionText); + } + + lastEdge = replacement.span.end.offset; + } + + var unmodifiedText = sourceFile.getText(lastEdge); + if (onUnmodified != null) { + onUnmodified(unmodifiedText); + } + } + + String getTransformedText() { + StringBuffer transformedSource = StringBuffer(); + + iterateReplacements( + onUnmodified: transformedSource.write, + onAddition: transformedSource.write); + + return transformedSource.toString(); + } + + String getHtmlDiff() { + const HtmlEscape elementEscaper = HtmlEscape(HtmlEscapeMode.element); + const HtmlEscape attrEscaper = HtmlEscape(HtmlEscapeMode.attribute); + + StringBuffer diff = StringBuffer(); + + void writeDiff(String source, String className) { + diff.write(''); + diff.write(elementEscaper.convert(source)); + diff.write(''); + } + + iterateReplacements( + onUnmodified: (source) => writeDiff(source, 'diff-unmodified'), + onRemoval: (source) => writeDiff(source, 'diff-removal'), + onAddition: (source) => writeDiff(source, 'diff-addition')); + + return ''' + + + + Transformer Diff - ${attrEscaper.convert(sourceFile.url!.path)} + + + + +
$diff
+
+ + + '''; + } +} + +SourceSpan getSpan(SourceFile sourceFile, AstNode node, + {bool skipCommentAndMetadata = true}) { + if (skipCommentAndMetadata && node is AnnotatedNode) { + return sourceFile.span( + node.firstTokenAfterCommentAndMetadata.offset, node.end); + } + + return sourceFile.span(node.offset, node.end); +} diff --git a/lib/src/builder/vendor/transformer_utils/transformer_utils.dart b/lib/src/builder/vendor/transformer_utils/transformer_utils.dart new file mode 100644 index 000000000..1ee559cd8 --- /dev/null +++ b/lib/src/builder/vendor/transformer_utils/transformer_utils.dart @@ -0,0 +1,26 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/lib/ + +library; + +export './src/analyzer_helpers.dart' + show copyClassMember, getDeclarationsAnnotatedBy, instantiateAnnotation; +export './src/barback_utils.dart' + show assetIdToPackageUri, getSpanForNode; +export './src/node_with_meta.dart' show NodeWithMeta; +export './src/text_util.dart' show stringLiteral; +export './src/transformed_source_file.dart' + show TransformedSourceFile, getSpan; diff --git a/pubspec.yaml b/pubspec.yaml index 83550b380..95b1ed28d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,6 @@ dependencies: react: ^7.3.1 redux: ^5.0.0 source_span: ^1.4.1 - transformer_utils: ^0.2.6 w_common: ^3.0.0 w_flux: ^3.0.0 platform_detect: ^2.0.0 diff --git a/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart b/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart new file mode 100644 index 000000000..31dae66d3 --- /dev/null +++ b/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart @@ -0,0 +1,66 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/test/ + +library; + +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:analyzer/dart/ast/ast.dart'; + +class TestAnnotation { + final positional; + final named; + final namedConstructorOnly; + const TestAnnotation(this.positional, {this.named}) + : namedConstructorOnly = null; + const TestAnnotation.namedConstructor({this.namedConstructorOnly}) + : positional = null, + named = null; +} + +ConstructorDeclaration? getConstructor(ClassDeclaration classDecl, + {String? name}) { + for (var member in classDecl.members) { + if (member is ConstructorDeclaration && member.name?.lexeme == name) { + return member; + } + } + return null; +} + +FieldDeclaration? getFieldByName(ClassDeclaration classDecl, String name) { + for (var member in classDecl.members) { + if (member is FieldDeclaration && + member.fields.variables.first.name.lexeme == name) { + return member; + } + } + return null; +} + +MethodDeclaration? getMethodByName(ClassDeclaration classDecl, String name) { + for (var member in classDecl.members) { + if (member is MethodDeclaration && member.name.lexeme == name) { + return member; + } + } + return null; +} + +T parseAndGetSingleMember(String source) { + var compilationUnit = + parseString(content: source, throwIfDiagnostics: false).unit; + return compilationUnit.declarations.single as T; +} diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart new file mode 100644 index 000000000..e441063f4 --- /dev/null +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart @@ -0,0 +1,386 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/test/ + +@TestOn('vm') +library; + +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:over_react/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart'; +import 'package:test/test.dart'; + + +import '../test_utils.dart'; + +const String classDef = ''' +class TestClass { + TestClass(); + + var field = 'field'; + String typedField = 'typedField'; + static String staticField = 'typedField'; + + get untypedGetter => null; + String get typedGetter => null; + static String get staticGetter => null; + Future get asyncGetter async => null; + + static void set staticSetter(v) { } + void set setter(v) { } + void set typedSetter(String v) { } + + untypedMethod() { } + String typedMethod() { } + static String staticMethod() { } + Future asyncMethod() async { } + String methodWithArgs(String a, {int b}) { } +} +'''; + +void expectLines(String actual, String expected) { + var expectedLines = expected.split('\n'); + var actualLines = actual.split('\n'); + for (var i = 0; i < expectedLines.length; i++) { + expect(actualLines[i].trim(), expectedLines[i].trim()); + } +} + +main() { + group('copyClassMember()', () { + test('throws on unsupported class member', () { + var node = parseAndGetSingleMember(classDef); + var ctor = getConstructor(node); + expect(() { + copyClassMember(ctor, 'body'); + }, throwsUnsupportedError); + }); + + group('FieldDeclaration', () { + test('copies an untyped field', () { + var node = parseAndGetSingleMember(classDef); + var field = getFieldByName(node, 'field'); + var expected = 'var field = "copy";'; + expect(copyClassMember(field, '"copy"'), equals(expected)); + }); + + test('copies a typed field', () { + var node = parseAndGetSingleMember(classDef); + var field = getFieldByName(node, 'typedField'); + var expected = 'String typedField = "copy";'; + expect(copyClassMember(field, '"copy"'), equals(expected)); + }); + + test('copies a static field', () { + var node = parseAndGetSingleMember(classDef); + var field = getFieldByName(node, 'staticField'); + var expected = 'static String staticField = "copy";'; + expect(copyClassMember(field, '"copy"'), equals(expected)); + }); + + test('copies a field witout an initializer', () { + var node = parseAndGetSingleMember(classDef); + var field = getFieldByName(node, 'typedField'); + var expected = 'String typedField;'; + expect(copyClassMember(field, ''), equals(expected)); + }); + }); + + group('MethodDeclaration (Getter)', () { + test('copies untyped getter', () { + var node = parseAndGetSingleMember(classDef); + var getter = getMethodByName(node, 'untypedGetter'); + var expected = + ['get untypedGetter {', 'return "copy";', '}'].join('\n'); + expectLines(copyClassMember(getter, 'return "copy";'), expected); + }); + + test('copies typed getter', () { + var node = parseAndGetSingleMember(classDef); + var getter = getMethodByName(node, 'typedGetter'); + var expected = + ['String get typedGetter {', 'return "copy";', '}'].join('\n'); + expectLines(copyClassMember(getter, 'return "copy";'), expected); + }); + + test('copies static getter', () { + var node = parseAndGetSingleMember(classDef); + var getter = getMethodByName(node, 'staticGetter'); + var expected = [ + 'static String get staticGetter {', + 'return "copy";', + '}' + ].join('\n'); + expectLines(copyClassMember(getter, 'return "copy";'), expected); + }); + + test('copies async getter', () { + var node = parseAndGetSingleMember(classDef); + var getter = getMethodByName(node, 'asyncGetter'); + var expected = ['Future get asyncGetter async {', 'return "copy";', '}'] + .join('\n'); + expectLines(copyClassMember(getter, 'return "copy";'), expected); + }); + }); + + group('MethodDeclaration (Setter)', () { + test('copies setter', () { + var node = parseAndGetSingleMember(classDef); + var setter = getMethodByName(node, 'setter'); + var expected = ['void set setter(v) {', 'c = "copy";', '}'].join('\n'); + expectLines(copyClassMember(setter, 'c = "copy";'), expected); + }); + + test('copies typed setter', () { + var node = parseAndGetSingleMember(classDef); + var setter = getMethodByName(node, 'typedSetter'); + var expected = + ['void set typedSetter(String v) {', 'c = "copy";', '}'].join('\n'); + expectLines(copyClassMember(setter, 'c = "copy";'), expected); + }); + + test('copies static setter', () { + var node = parseAndGetSingleMember(classDef); + var setter = getMethodByName(node, 'staticSetter'); + var expected = ['static void set staticSetter(v) {', 'c = "copy";', '}'] + .join('\n'); + expectLines(copyClassMember(setter, 'c = "copy";'), expected); + }); + }); + + group('MethodDeclaration', () { + test('copies untyped method', () { + var node = parseAndGetSingleMember(classDef); + var method = getMethodByName(node, 'untypedMethod'); + var expected = ['untypedMethod() {', 'c = "copy";', '}'].join('\n'); + expectLines(copyClassMember(method, 'c = "copy";'), expected); + }); + + test('copies typed method', () { + var node = parseAndGetSingleMember(classDef); + var method = getMethodByName(node, 'typedMethod'); + var expected = + ['String typedMethod() {', 'c = "copy";', '}'].join('\n'); + expectLines(copyClassMember(method, 'c = "copy";'), expected); + }); + + test('copies static method', () { + var node = parseAndGetSingleMember(classDef); + var method = getMethodByName(node, 'staticMethod'); + var expected = + ['static String staticMethod() {', 'c = "copy";', '}'].join('\n'); + expectLines(copyClassMember(method, 'c = "copy";'), expected); + }); + + test('copies async method', () async { + var node = parseAndGetSingleMember(classDef); + var method = getMethodByName(node, 'asyncMethod'); + var expected = + ['Future asyncMethod() async {', 'c = "copy";', '}'].join('\n'); + expectLines(copyClassMember(method, 'c = "copy";'), expected); + }); + + test('copies method with args', () async { + var node = parseAndGetSingleMember(classDef); + var method = getMethodByName(node, 'methodWithArgs'); + var expected = [ + 'String methodWithArgs(String a, {int b}) {', + 'c = "copy";', + '}' + ].join('\n'); + expectLines(copyClassMember(method, 'c = "copy";'), expected); + }); + }); + }); + + group('getDeclarationsAnnotatedBy()', () { + test('no matching declarations', () { + var result = parseString( + content: + ['var a;', '@OtherAnnotation()', 'var b;', 'var c;'].join('\n'), + throwIfDiagnostics: false); + var matches = getDeclarationsAnnotatedBy(result.unit, TestAnnotation); + expect(matches, isEmpty); + }); + + test('one matching declaration', () { + var result = parseString( + content: [ + '@TestAnnotation("test")', + 'var a;', + '@OtherAnnotation()', + 'var b;', + 'var c;' + ].join('\n'), + throwIfDiagnostics: false); + var matches = getDeclarationsAnnotatedBy(result.unit, TestAnnotation); + expect(matches.length, 1); + }); + + test('multiple matching declarations', () { + var result = parseString( + content: [ + '@TestAnnotation("test")', + 'var a;', + '@OtherAnnotation()', + 'var b;', + '@TestAnnotation("test")', + 'var c;' + ].join('\n'), + throwIfDiagnostics: false); + var matches = getDeclarationsAnnotatedBy(result.unit, TestAnnotation); + expect(matches.length, 2); + }); + }); + + group('instantiateAnnotation()', () { + group('instantiates an annotation with a parameter value specified as', () { + test('a string literal', () { + var node = parseAndGetSingleMember('@TestAnnotation("hello")\nvar a;'); + TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + expect(instance.positional, "hello"); + }); + + test('a concatenated string literal', () { + var node = parseAndGetSingleMember('@TestAnnotation("he" "y")\nvar a;'); + TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + expect(instance.positional, "hey"); + }); + + test('a boolean literal', () { + var node = parseAndGetSingleMember('@TestAnnotation(true)\nvar a;'); + TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + expect(instance.positional, true); + }); + + test('an integer literal', () { + var node = parseAndGetSingleMember('@TestAnnotation(1)\nvar a;'); + TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + expect(instance.positional, 1); + }); + + test('a null literal', () { + var node = parseAndGetSingleMember('@TestAnnotation(null)\nvar a;'); + TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + expect(instance.positional, null); + }); + }); + + group('throws when an annotation parameter value is unsupported:', () { + test('a constant expression', () { + var node = parseAndGetSingleMember('@TestAnnotation(const [])\nvar a;'); + expect(() => instantiateAnnotation(node, TestAnnotation), + throwsUnsupportedError); + }); + + test('an interpolated String', () { + var node = parseAndGetSingleMember('@TestAnnotation("\$v")\nvar a;'); + expect(() => instantiateAnnotation(node, TestAnnotation), + throwsUnsupportedError); + }); + + test('an identifier', () { + var node = + parseAndGetSingleMember('@TestAnnotation(identifier)\nvar a;'); + expect(() => instantiateAnnotation(node, TestAnnotation), + throwsUnsupportedError); + }); + + group('(except when `onUnsupportedArgument` is specified)', () { + test('positional parameter', () { + Expression? unsupportedArgument; + + TestAnnotation instance = instantiateAnnotation( + parseAndGetSingleMember('@TestAnnotation(const [])\nvar a;'), + TestAnnotation, onUnsupportedArgument: (Expression expression) { + unsupportedArgument = expression; + return 'value to be passed to constructor instead'; + }); + + expect(unsupportedArgument, isA()); + expect(instance.positional, + equals('value to be passed to constructor instead'), + reason: + 'should have passed the return value of `onUnsupportedArgument` to the constructor'); + }); + + test('named parameter', () { + Expression? unsupportedArgument; + + TestAnnotation instance = instantiateAnnotation( + parseAndGetSingleMember( + '@TestAnnotation.namedConstructor(namedConstructorOnly: const [])\nvar a;'), + TestAnnotation, onUnsupportedArgument: (Expression expression) { + unsupportedArgument = expression; + return 'value to be passed to constructor instead'; + }); + + expect(unsupportedArgument, isA()); + expect((unsupportedArgument as NamedExpression).name.label.name, + equals('namedConstructorOnly')); + expect(instance.namedConstructorOnly, + equals('value to be passed to constructor instead'), + reason: + 'should have passed the return value of `onUnsupportedArgument` to the constructor'); + }); + }); + }); + + test('annotation with both named and positional parameters', () { + var node = + parseAndGetSingleMember('@TestAnnotation(1, named: 2)\nvar a;'); + TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + expect(instance.positional, 1); + expect(instance.named, 2); + }); + + test('instantiates an annotation using a named constructor', () { + var node = parseAndGetSingleMember( + '@TestAnnotation.namedConstructor(namedConstructorOnly: true)\nvar a;'); + TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + expect(instance.namedConstructorOnly, true); + }); + + test('throws if the annotation cannot be constructed', () { + var node = parseAndGetSingleMember( + '@TestAnnotation(1, 2, 3, 4, "way more parameters than were declared")\nvar a;'); + expect(() { + instantiateAnnotation(node, TestAnnotation); + }, throwsA(startsWith('Unable to instantiate annotation'))); + }); + + test('throws if the annotation is not used as a constructor', () { + var node = parseAndGetSingleMember('@TestAnnotation\nvar a;'); + expect(() { + instantiateAnnotation(node, TestAnnotation); + }, throwsA(startsWith('Annotation not invocation of constructor'))); + }); + + test('returns null when the member is not annotated', () { + var node = parseAndGetSingleMember('var a;'); + expect(instantiateAnnotation(node, TestAnnotation), isNull); + }); + + test('returns null when the member has only non-matching annotations', () { + var node = parseAndGetSingleMember('@NonexistantAnnotation\nvar a;'); + expect(instantiateAnnotation(node, TestAnnotation), isNull); + }); + + test('returns null when the member has no annotations', () { + var node = parseAndGetSingleMember('var a;'); + expect(instantiateAnnotation(node, TestAnnotation), isNull); + }); + }); +} diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/barback_utils_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/barback_utils_test.dart new file mode 100644 index 000000000..c07afe95c --- /dev/null +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/barback_utils_test.dart @@ -0,0 +1,84 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/test/ + +@TestOn('vm') +library; + +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:build/build.dart'; +import 'package:over_react/src/builder/vendor/transformer_utils/src/barback_utils.dart'; +import 'package:source_span/source_span.dart'; +import 'package:test/test.dart'; + +const String sourceFileText = ''' +var plainVar = "plain"; + +/// Comment +@Annotation() +var varWithCommentAndMeta = "annotated"; +'''; + +void main() { + group('assetIdToPackageUri()', () { + test('returns original path for non-lib file', () { + var assetId = AssetId('transformer_utils', 'test/test_utils.dart'); + expect(assetIdToPackageUri(assetId), Uri.parse('test/test_utils.dart')); + }); + + test('returns path with "package" scheme for lib file', () { + var assetId = AssetId('transformer_utils', 'lib/transformer_utils.dart'); + expect(assetIdToPackageUri(assetId), + Uri.parse('package:over_react/src/builder/vendor/transformer_utils/transformer_utils.dart')); + }); + }); + + group('getSpanForNode()', () { + test('should get node but skip comment and meta by default', () { + var sourceFile = SourceFile.fromString(sourceFileText); + var unit = + parseString(content: sourceFileText, throwIfDiagnostics: false).unit; + var annotatedNode = unit.childEntities.last as AstNode; + var span = getSpanForNode(sourceFile, annotatedNode); + expect(span.text, 'var varWithCommentAndMeta = "annotated";'); + }); + + test('should not skip comment and meta if skip is false', () { + var sourceFile = SourceFile.fromString(sourceFileText); + var unit = + parseString(content: sourceFileText, throwIfDiagnostics: false).unit; + var annotatedNode = unit.childEntities.last as AstNode; + var span = getSpanForNode(sourceFile, annotatedNode, + skipCommentAndMetadata: false); + expect( + span.text, + [ + '/// Comment', + '@Annotation()', + 'var varWithCommentAndMeta = "annotated";' + ].join('\n')); + }); + + test('should return the whole span if the node is not annotated', () { + var sourceFile = SourceFile.fromString(sourceFileText); + var unit = + parseString(content: sourceFileText, throwIfDiagnostics: false).unit; + var plainNode = unit.childEntities.first as AstNode; + var span = getSpanForNode(sourceFile, plainNode); + expect(span.text, 'var plainVar = "plain";'); + }); + }); +} diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/node_with_meta_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/node_with_meta_test.dart new file mode 100644 index 000000000..6d40676af --- /dev/null +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/node_with_meta_test.dart @@ -0,0 +1,77 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/test/ + +@TestOn('vm') +library; + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:test/test.dart'; + +import 'package:over_react/src/builder/vendor/transformer_utils/transformer_utils.dart'; + +import '../test_utils.dart'; + +void main() { + group('NodeWithMeta', () { + test('instantiates and provides access to an annotation and node', () { + var member = parseAndGetSingleMember('@TestAnnotation("hello")\nvar a;'); + var nodeWithMeta = + NodeWithMeta( + member as TopLevelVariableDeclaration); + + expect(nodeWithMeta.node, same(member)); + expect(nodeWithMeta.metaNode, isNotNull); + expect(nodeWithMeta.metaNode!.name.name, 'TestAnnotation'); + expect(nodeWithMeta.meta, isNotNull); + expect(nodeWithMeta.meta!.positional, 'hello'); + }); + + test('partially instantiates an "incomplete" annotation', () { + var member = parseAndGetSingleMember( + '@TestAnnotation(someIdentifier, named: "hello")\nvar a;'); + var nodeWithMeta = + NodeWithMeta( + member as TopLevelVariableDeclaration); + + expect(nodeWithMeta.node, same(member)); + expect(nodeWithMeta.metaNode, isNotNull); + expect(nodeWithMeta.metaNode!.name.name, 'TestAnnotation'); + + expect(nodeWithMeta.isIncomplete, isTrue); + expect(nodeWithMeta.unsupportedArguments, hasLength(1)); + expect(() => nodeWithMeta.meta, throwsStateError); + + expect(nodeWithMeta.potentiallyIncompleteMeta, isNotNull, + reason: + 'should still have attempted to instantiate the incomplete annotation'); + expect(nodeWithMeta.potentiallyIncompleteMeta!.named, equals('hello'), + reason: 'should still have passed the supported argument'); + expect(nodeWithMeta.potentiallyIncompleteMeta!.positional, isNull, + reason: 'should have used null for unsupported argument'); + }); + + test('gracefully handles a node without an annotation', () { + var member = parseAndGetSingleMember('var a;'); + var nodeWithMeta = + NodeWithMeta( + member as TopLevelVariableDeclaration); + + expect(nodeWithMeta.node, same(member)); + expect(nodeWithMeta.metaNode, isNull); + expect(nodeWithMeta.meta, isNull); + }); + }); +} diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/text_util_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/text_util_test.dart new file mode 100644 index 000000000..ca6a6f559 --- /dev/null +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/text_util_test.dart @@ -0,0 +1,125 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/test/ + +@TestOn('vm') +library; + +import 'package:over_react/src/builder/vendor/transformer_utils/src/text_util.dart'; +import 'package:test/test.dart'; + +main() { + group('stringLiteral()', () { + test('optionally quotes strings', () { + expect(stringLiteral('unquoted', quote: false), "unquoted"); + expect(stringLiteral('unquoted', useSingleQuote: true, quote: false), + 'unquoted'); + expect(stringLiteral('quoted'), "'quoted'"); + expect(stringLiteral('quoted', useSingleQuote: false), '"quoted"'); + }); + + test('escapes quotes', () { + expect(stringLiteral(r"'", quote: false), r"\'"); + expect(stringLiteral(r'"', useSingleQuote: false, quote: false), r'\"'); + }); + + test('does not escape the other kind of quotes', () { + expect(stringLiteral(r"'", useSingleQuote: false, quote: false), r"'"); + expect(stringLiteral(r'"', quote: false), r'"'); + }); + + test('escapes backslashes', () { + expect(stringLiteral(r'\', quote: false), r'\\'); + }); + + test('escapes dollar signs', () { + expect(stringLiteral(r'$', quote: false), r'\$'); + }); + + test('escapes special characters', () { + // Adapted from dart.convert library's JSON encoder: + // https://github.com/dart-lang/sdk/blob/1.12.0/tests/lib/convert/json_util_test.dart#L138 + // + // + // Copyright 2012, the Dart project authors. All rights reserved. + // Redistribution and use in source and binary forms, with or without + // modification, are permitted provided that the following conditions are + // met: + // * Redistributions of source code must retain the above copyright + // notice, this list of conditions and the following disclaimer. + // * Redistributions in binary form must reproduce the above + // copyright notice, this list of conditions and the following + // disclaimer in the documentation and/or other materials provided + // with the distribution. + // * Neither the name of Google Inc. nor the names of its + // contributors may be used to endorse or promote products derived + // from this software without specific prior written permission. + // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + expect(stringLiteral('\u0000', quote: false), '\\u0000'); + expect(stringLiteral('\u0001', quote: false), '\\u0001'); + expect(stringLiteral('\u0002', quote: false), '\\u0002'); + expect(stringLiteral('\u0003', quote: false), '\\u0003'); + expect(stringLiteral('\u0004', quote: false), '\\u0004'); + expect(stringLiteral('\u0005', quote: false), '\\u0005'); + expect(stringLiteral('\u0006', quote: false), '\\u0006'); + expect(stringLiteral('\u0007', quote: false), '\\u0007'); + expect(stringLiteral('\u0008', quote: false), '\\b'); + expect(stringLiteral('\u0009', quote: false), '\\t'); + expect(stringLiteral('\u000a', quote: false), '\\n'); + expect(stringLiteral('\u000b', quote: false), '\\u000b'); + expect(stringLiteral('\u000c', quote: false), '\\f'); + expect(stringLiteral('\u000d', quote: false), '\\r'); + expect(stringLiteral('\u000e', quote: false), '\\u000e'); + expect(stringLiteral('\u000f', quote: false), '\\u000f'); + expect(stringLiteral('\u0010', quote: false), '\\u0010'); + expect(stringLiteral('\u0011', quote: false), '\\u0011'); + expect(stringLiteral('\u0012', quote: false), '\\u0012'); + expect(stringLiteral('\u0013', quote: false), '\\u0013'); + expect(stringLiteral('\u0014', quote: false), '\\u0014'); + expect(stringLiteral('\u0015', quote: false), '\\u0015'); + expect(stringLiteral('\u0016', quote: false), '\\u0016'); + expect(stringLiteral('\u0017', quote: false), '\\u0017'); + expect(stringLiteral('\u0018', quote: false), '\\u0018'); + expect(stringLiteral('\u0019', quote: false), '\\u0019'); + expect(stringLiteral('\u001a', quote: false), '\\u001a'); + expect(stringLiteral('\u001b', quote: false), '\\u001b'); + expect(stringLiteral('\u001c', quote: false), '\\u001c'); + expect(stringLiteral('\u001d', quote: false), '\\u001d'); + expect(stringLiteral('\u001e', quote: false), '\\u001e'); + expect(stringLiteral('\u001f', quote: false), '\\u001f'); + expect( + stringLiteral('Got \b, \f, \n, \r, \t, \u0000, \\, and ".', + quote: false), + 'Got \\b, \\f, \\n, \\r, \\t, \\u0000, \\\\, and ".'); + expect(stringLiteral('Got \b\f\n\r\t\u0000\\".', quote: false), + 'Got \\b\\f\\n\\r\\t\\u0000\\\\".'); + }); + + test('writes the null literal for an input of null', () { + expect(stringLiteral(null, quote: false), 'null'); + expect(stringLiteral(null), 'null'); + }); + }); +} diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/transformed_source_file_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/transformed_source_file_test.dart new file mode 100644 index 000000000..33e0b5fbd --- /dev/null +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/transformed_source_file_test.dart @@ -0,0 +1,243 @@ +// Copyright 2015 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copied from https://github.com/Workiva/dart_transformer_utils/tree/0.2.23/test/ + +@TestOn('vm') +library; + +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:over_react/src/builder/vendor/transformer_utils/src/transformed_source_file.dart'; +import 'package:source_span/source_span.dart'; +import 'package:test/test.dart'; + + +main() { + group('TransformedSourceFile', () { + late SourceFile testSourceFile; + late TransformedSourceFile transformedFile; + + setUp(() { + testSourceFile = + SourceFile.fromString('0123456789', url: 'test_source_file.dart'); + transformedFile = TransformedSourceFile(testSourceFile); + }); + + group('replace()', () { + test('replaces a span of text and outputs the result', () { + transformedFile.replace(testSourceFile.span(3, 8), '{replaced}'); + expect(transformedFile.getTransformedText(), '012{replaced}89'); + }); + + test('replaces multiple spans of text and outputs the result', () { + transformedFile.replace(testSourceFile.span(1, 2), '{replaced 1}'); + transformedFile.replace(testSourceFile.span(6, 9), '{replaced 2}'); + transformedFile.replace(testSourceFile.span(4, 6), '{replaced 3}'); + expect(transformedFile.getTransformedText(), + '0{replaced 1}23{replaced 3}{replaced 2}9'); + }); + + test('inserts text and outputs the result', () { + transformedFile.insert(testSourceFile.location(4), '{inserted}'); + expect(transformedFile.getTransformedText(), '0123{inserted}456789'); + }); + }); + + group('insert()', () { + test('inserts multiple pieces of text and outputs the result', () { + transformedFile.insert(testSourceFile.location(1), '{inserted 1}'); + transformedFile.insert(testSourceFile.location(6), '{inserted 2}'); + transformedFile.insert(testSourceFile.location(4), '{inserted 3}'); + expect(transformedFile.getTransformedText(), + '0{inserted 1}123{inserted 3}45{inserted 2}6789'); + }); + + test('throws if sections of replaced text overlap each other', () { + transformedFile.replace(testSourceFile.span(1, 3), '{replaced 1}'); + transformedFile.replace(testSourceFile.span(0, 2), '{replaced 2}'); + expect(() => transformedFile.getTransformedText(), + throwsA(startsWith('Overlapping replacement'))); + }); + }); + + group('remove()', () { + const String text = 'line 0\nline 1\nline 2\nline 3\nline 4'; + late SourceSpan lines1Through3Span; + + setUp(() { + testSourceFile = SourceFile.fromString(text); + transformedFile = TransformedSourceFile(testSourceFile); + + var lines1Through3 = '\nline 1\nline 2\nline 3'.allMatches(text).single; + lines1Through3Span = + testSourceFile.span(lines1Through3.start, lines1Through3.end); + }); + + test('removes spans of text, removing newlines by default', () { + transformedFile.remove(lines1Through3Span); + expect(transformedFile.getTransformedText(), 'line 0\nline 4'); + }); + + test('removes spans of text, preserving newlines when specified', () { + transformedFile.remove(lines1Through3Span, preserveNewlines: true); + expect(transformedFile.getTransformedText(), 'line 0\n\n\n\nline 4'); + }); + }); + + group('`isModified`', () { + test('is true when a replacement has been made', () { + transformedFile.replace(testSourceFile.span(3, 8), '{replaced}'); + expect(transformedFile.isModified, isTrue); + }); + + test('is true when an insertion has been made', () { + transformedFile.insert(testSourceFile.location(3), '{replaced}'); + expect(transformedFile.isModified, isTrue); + }); + + test('is false for a pristine TransformedSourceFile', () { + expect(transformedFile.isModified, isFalse); + }); + }); + + group('iterateReplacements()', () { + test('iterates through the replacements using the specified callbacks', + () { + var unmodifieds = []; + var additions = []; + var removals = []; + + var all = []; + + transformedFile.remove(testSourceFile.span(2, 3)); + transformedFile.remove(testSourceFile.span(8, 9)); + transformedFile.insert(testSourceFile.location(1), '{inserted 1}'); + transformedFile.insert(testSourceFile.location(7), '{inserted 2}'); + transformedFile.replace(testSourceFile.span(5, 7), '{replaced 1}'); + transformedFile.replace(testSourceFile.span(0, 1), '{replaced 2}'); + + transformedFile.iterateReplacements(onUnmodified: (text) { + unmodifieds.add(text); + all.add(text); + }, onRemoval: (text) { + removals.add(text); + all.add(text); + }, onAddition: (text) { + additions.add(text); + all.add(text); + }); + + expect(unmodifieds, equals(['1', '34', '7', '9'])); + expect( + additions, + equals([ + '{replaced 2}', + '{inserted 1}', + '{replaced 1}', + '{inserted 2}' + ])); + expect(removals, equals(['0', '2', '56', '8'])); + + expect( + all, + equals([ + '0', // - removal + '{replaced 2}', // + addition + '{inserted 1}', // + addition + '1', // unmodified + '2', // - removal + '34', // unmodified + '56', // - removal + '{replaced 1}', // + addition + '{inserted 2}', // + addition + '7', // unmodified + '8', // - removal + '9' // unmodified + ]), + reason: 'should have called all parts in the right order'); + }); + }); + + group('getHtmlDiff()', () { + test('returns an HTML page containing the diff of the modified file', () { + transformedFile.remove(testSourceFile.span(2, 3)); + transformedFile.remove(testSourceFile.span(8, 9)); + transformedFile.insert(testSourceFile.location(1), '{inserted 1}'); + transformedFile.insert(testSourceFile.location(7), '{inserted 2}'); + transformedFile.replace(testSourceFile.span(5, 7), '{replaced 1}'); + transformedFile.replace(testSourceFile.span(0, 1), '{replaced 2}'); + + var html = transformedFile.getHtmlDiff(); + + expect(html, matches(RegExp(r'^\s*<\!DOCTYPE html>\s*'))); + expect( + html, + contains('Transformer Diff - ' + 'test_source_file.dart' + '')); + expect( + html, + contains('
'
+                '0'
+                '{replaced 2}'
+                '{inserted 1}'
+                '1'
+                '2'
+                '34'
+                '56'
+                '{replaced 1}'
+                '{inserted 2}'
+                '7'
+                '8'
+                '9'
+                '
')); + expect(html, matches(RegExp(r'\s*$'))); + }); + }); + }); + + group('getSpan()', () { + const String source = '\n' + '/// Doc comment\n' + '@Annotation\n' + 'class Node {}\n'; + + late SourceFile sourceFile; + late AnnotatedNode node; + + setUp(() { + sourceFile = SourceFile.fromString(source); + node = parseString(content: source, throwIfDiagnostics: false) + .unit + .declarations + .single; + }); + + test( + 'returns a SourceSpan for a node, skipping its doc comments and meta by default', + () { + var span = getSpan(sourceFile, node); + expect(span.start.offset, equals(source.indexOf('class'))); + expect(span.end.offset, equals(source.indexOf('}') + 1)); + }); + + test('returns a SourceSpan for a node, including its doc comments and meta', + () { + var span = getSpan(sourceFile, node, skipCommentAndMetadata: false); + expect(span.start.offset, equals(source.indexOf('///'))); + expect(span.end.offset, equals(source.indexOf('}') + 1)); + }); + }); +} From fbe4a320858ae8fd7c06b1a2dbd11ce329929ca9 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Mon, 9 Feb 2026 15:48:03 -0700 Subject: [PATCH 2/6] Fix implicit_cast errors --- .../transformer_utils/src/node_with_meta.dart | 2 +- .../unit/analyzer_helpers_test.dart | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart b/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart index b2c68a5d7..15f3d7e0f 100644 --- a/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart +++ b/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart @@ -47,7 +47,7 @@ class NodeWithMeta { : this.node = node, this.metaNode = getMatchingAnnotation(node, TMeta) { this._meta = instantiateAnnotation(node, TMeta, - onUnsupportedArgument: unsupportedArguments.add); + onUnsupportedArgument: unsupportedArguments.add) as TMeta?; } /// Whether this node's metadata has arguments that could not be initialized using [getValue] diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart index e441063f4..5abfa336d 100644 --- a/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart @@ -249,31 +249,31 @@ main() { group('instantiates an annotation with a parameter value specified as', () { test('a string literal', () { var node = parseAndGetSingleMember('@TestAnnotation("hello")\nvar a;'); - TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + final instance = instantiateAnnotation(node, TestAnnotation) as TestAnnotation; expect(instance.positional, "hello"); }); test('a concatenated string literal', () { var node = parseAndGetSingleMember('@TestAnnotation("he" "y")\nvar a;'); - TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + final instance = instantiateAnnotation(node, TestAnnotation) as TestAnnotation; expect(instance.positional, "hey"); }); test('a boolean literal', () { var node = parseAndGetSingleMember('@TestAnnotation(true)\nvar a;'); - TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + final instance = instantiateAnnotation(node, TestAnnotation) as TestAnnotation; expect(instance.positional, true); }); test('an integer literal', () { var node = parseAndGetSingleMember('@TestAnnotation(1)\nvar a;'); - TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + final instance = instantiateAnnotation(node, TestAnnotation) as TestAnnotation; expect(instance.positional, 1); }); test('a null literal', () { var node = parseAndGetSingleMember('@TestAnnotation(null)\nvar a;'); - TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + final instance = instantiateAnnotation(node, TestAnnotation) as TestAnnotation; expect(instance.positional, null); }); }); @@ -302,12 +302,12 @@ main() { test('positional parameter', () { Expression? unsupportedArgument; - TestAnnotation instance = instantiateAnnotation( + final instance = instantiateAnnotation( parseAndGetSingleMember('@TestAnnotation(const [])\nvar a;'), TestAnnotation, onUnsupportedArgument: (Expression expression) { unsupportedArgument = expression; return 'value to be passed to constructor instead'; - }); + }) as TestAnnotation; expect(unsupportedArgument, isA()); expect(instance.positional, @@ -319,13 +319,13 @@ main() { test('named parameter', () { Expression? unsupportedArgument; - TestAnnotation instance = instantiateAnnotation( + final instance = instantiateAnnotation( parseAndGetSingleMember( '@TestAnnotation.namedConstructor(namedConstructorOnly: const [])\nvar a;'), TestAnnotation, onUnsupportedArgument: (Expression expression) { unsupportedArgument = expression; return 'value to be passed to constructor instead'; - }); + }) as TestAnnotation; expect(unsupportedArgument, isA()); expect((unsupportedArgument as NamedExpression).name.label.name, @@ -341,7 +341,7 @@ main() { test('annotation with both named and positional parameters', () { var node = parseAndGetSingleMember('@TestAnnotation(1, named: 2)\nvar a;'); - TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + final instance = instantiateAnnotation(node, TestAnnotation) as TestAnnotation; expect(instance.positional, 1); expect(instance.named, 2); }); @@ -349,7 +349,7 @@ main() { test('instantiates an annotation using a named constructor', () { var node = parseAndGetSingleMember( '@TestAnnotation.namedConstructor(namedConstructorOnly: true)\nvar a;'); - TestAnnotation instance = instantiateAnnotation(node, TestAnnotation); + final instance = instantiateAnnotation(node, TestAnnotation) as TestAnnotation; expect(instance.namedConstructorOnly, true); }); From 7e4f9b4ac568e942e522c33b9ee3862defa227e0 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Mon, 9 Feb 2026 15:53:31 -0700 Subject: [PATCH 3/6] Fix lint warnings --- .../transformer_utils/src/analyzer_helpers.dart | 12 ++++++------ .../vendor/transformer_utils/src/node_with_meta.dart | 5 ++--- .../src/transformed_source_file.dart | 8 ++++---- .../builder/vendor/transformer_utils/test_utils.dart | 6 +++--- .../unit/analyzer_helpers_test.dart | 6 +++--- .../transformer_utils/unit/text_util_test.dart | 2 +- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart b/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart index 47409b022..c83a1c1ad 100644 --- a/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart +++ b/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart @@ -65,7 +65,7 @@ Iterable getDeclarationsAnnotatedBy( /// * [IntegerLiteral] /// * [NullLiteral] dynamic getValue(Expression expression, - {dynamic onUnsupportedExpression(Expression expression)?}) { + {dynamic Function(Expression expression)? onUnsupportedExpression}) { if (expression is StringLiteral) { var value = expression.stringValue; if (value != null) { @@ -108,7 +108,7 @@ Annotation? getMatchingAnnotation(AnnotatedNode member, Type annotationType) { /// /// Naively assumes that the name of the [annotationType] class is canonical. dynamic instantiateAnnotation(AnnotatedNode member, Type annotationType, - {dynamic onUnsupportedArgument(Expression argument)?}) { + {dynamic Function(Expression argument)? onUnsupportedArgument}) { final matchingAnnotation = getMatchingAnnotation(member, annotationType); // If no annotation is found, return null. @@ -118,9 +118,9 @@ dynamic instantiateAnnotation(AnnotatedNode member, Type annotationType, final matchingAnnotationArgs = matchingAnnotation.arguments; if (matchingAnnotationArgs == null) { - throw 'Annotation not invocation of constructor: `$matchingAnnotation`. ' + throw Exception('Annotation not invocation of constructor: `$matchingAnnotation`. ' 'This is likely due to invalid usage of the annotation class, but could' - 'also be a name conflict with the specified type `$annotationType`'; + 'also be a name conflict with the specified type `$annotationType`'); } // Get the parameters from the annotation's AST. @@ -158,9 +158,9 @@ dynamic instantiateAnnotation(AnnotatedNode member, Type annotationType, Symbol(constructorName), positionalParameters, namedParameters); return instanceMirror.reflectee; } catch (e, stacktrace) { - throw 'Unable to instantiate annotation: $matchingAnnotation. This is ' + throw Exception('Unable to instantiate annotation: $matchingAnnotation. This is ' 'likely due to improper usage, or a naming conflict with ' - 'annotationType $annotationType. Original error: $e. Stacktrace: $stacktrace'; + 'annotationType $annotationType. Original error: $e. Stacktrace: $stacktrace'); } } diff --git a/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart b/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart index 15f3d7e0f..f83970bfb 100644 --- a/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart +++ b/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart @@ -43,9 +43,8 @@ class NodeWithMeta { /// Construct a [NodeWithMeta] instance from an [AnnotatedNode]. /// The original node will be available via [node]. /// The instantiated annotation of type `TMeta` will be available via [meta]. - NodeWithMeta(TNode node, {this.assetId}) - : this.node = node, - this.metaNode = getMatchingAnnotation(node, TMeta) { + NodeWithMeta(this.node, {this.assetId}) + : this.metaNode = getMatchingAnnotation(node, TMeta) { this._meta = instantiateAnnotation(node, TMeta, onUnsupportedArgument: unsupportedArguments.add) as TMeta?; } diff --git a/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart b/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart index 398545404..252eeb82f 100644 --- a/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart +++ b/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart @@ -64,15 +64,15 @@ class TransformedSourceFile { } void iterateReplacements( - {onUnmodified(String string)?, - onRemoval(String string)?, - onAddition(String string)?}) { + {Function(String string)? onUnmodified, + Function(String string)? onRemoval, + Function(String string)? onAddition}) { _replacements.sort((r1, r2) => r1.span.compareTo(r2.span)); var lastEdge = 0; for (_Replacement replacement in _replacements) { if (replacement.span.start.offset < lastEdge) { - throw 'Overlapping replacement $replacement in replacements $_replacements.'; + throw Exception('Overlapping replacement $replacement in replacements $_replacements.'); } var unmodifiedText = diff --git a/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart b/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart index 31dae66d3..ed9b2c5f8 100644 --- a/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart +++ b/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart @@ -20,9 +20,9 @@ import 'package:analyzer/dart/analysis/utilities.dart'; import 'package:analyzer/dart/ast/ast.dart'; class TestAnnotation { - final positional; - final named; - final namedConstructorOnly; + final dynamic positional; + final dynamic named; + final dynamic namedConstructorOnly; const TestAnnotation(this.positional, {this.named}) : namedConstructorOnly = null; const TestAnnotation.namedConstructor({this.namedConstructorOnly}) diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart index 5abfa336d..6dad16269 100644 --- a/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart @@ -250,13 +250,13 @@ main() { test('a string literal', () { var node = parseAndGetSingleMember('@TestAnnotation("hello")\nvar a;'); final instance = instantiateAnnotation(node, TestAnnotation) as TestAnnotation; - expect(instance.positional, "hello"); + expect(instance.positional, 'hello'); }); test('a concatenated string literal', () { var node = parseAndGetSingleMember('@TestAnnotation("he" "y")\nvar a;'); final instance = instantiateAnnotation(node, TestAnnotation) as TestAnnotation; - expect(instance.positional, "hey"); + expect(instance.positional, 'hey'); }); test('a boolean literal', () { @@ -328,7 +328,7 @@ main() { }) as TestAnnotation; expect(unsupportedArgument, isA()); - expect((unsupportedArgument as NamedExpression).name.label.name, + expect((unsupportedArgument! as NamedExpression).name.label.name, equals('namedConstructorOnly')); expect(instance.namedConstructorOnly, equals('value to be passed to constructor instead'), diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/text_util_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/text_util_test.dart index ca6a6f559..8ad077542 100644 --- a/test/vm_tests/builder/vendor/transformer_utils/unit/text_util_test.dart +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/text_util_test.dart @@ -23,7 +23,7 @@ import 'package:test/test.dart'; main() { group('stringLiteral()', () { test('optionally quotes strings', () { - expect(stringLiteral('unquoted', quote: false), "unquoted"); + expect(stringLiteral('unquoted', quote: false), 'unquoted'); expect(stringLiteral('unquoted', useSingleQuote: true, quote: false), 'unquoted'); expect(stringLiteral('quoted'), "'quoted'"); From e99f44ba78a15467c67ff71ed64c4d7a70315701 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Mon, 9 Feb 2026 16:02:41 -0700 Subject: [PATCH 4/6] Fix test failures --- .../vm_tests/builder/vendor/transformer_utils/test_utils.dart | 3 +++ .../vendor/transformer_utils/unit/analyzer_helpers_test.dart | 4 ++-- .../vendor/transformer_utils/unit/barback_utils_test.dart | 2 +- .../transformer_utils/unit/transformed_source_file_test.dart | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart b/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart index ed9b2c5f8..b8ef2b0b8 100644 --- a/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart +++ b/test/vm_tests/builder/vendor/transformer_utils/test_utils.dart @@ -18,6 +18,7 @@ library; import 'package:analyzer/dart/analysis/utilities.dart'; import 'package:analyzer/dart/ast/ast.dart'; +import 'package:test/test.dart'; class TestAnnotation { final dynamic positional; @@ -64,3 +65,5 @@ T parseAndGetSingleMember(String source) { parseString(content: source, throwIfDiagnostics: false).unit; return compilationUnit.declarations.single as T; } + +Matcher hasToStringValue(dynamic value) => isA().having((e) => e.toString(), 'toString', value); diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart index 6dad16269..25c35e875 100644 --- a/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/analyzer_helpers_test.dart @@ -358,14 +358,14 @@ main() { '@TestAnnotation(1, 2, 3, 4, "way more parameters than were declared")\nvar a;'); expect(() { instantiateAnnotation(node, TestAnnotation); - }, throwsA(startsWith('Unable to instantiate annotation'))); + }, throwsA(hasToStringValue(contains('Unable to instantiate annotation')))); }); test('throws if the annotation is not used as a constructor', () { var node = parseAndGetSingleMember('@TestAnnotation\nvar a;'); expect(() { instantiateAnnotation(node, TestAnnotation); - }, throwsA(startsWith('Annotation not invocation of constructor'))); + }, throwsA(hasToStringValue(contains('Annotation not invocation of constructor')))); }); test('returns null when the member is not annotated', () { diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/barback_utils_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/barback_utils_test.dart index c07afe95c..1abc6c75b 100644 --- a/test/vm_tests/builder/vendor/transformer_utils/unit/barback_utils_test.dart +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/barback_utils_test.dart @@ -42,7 +42,7 @@ void main() { test('returns path with "package" scheme for lib file', () { var assetId = AssetId('transformer_utils', 'lib/transformer_utils.dart'); expect(assetIdToPackageUri(assetId), - Uri.parse('package:over_react/src/builder/vendor/transformer_utils/transformer_utils.dart')); + Uri.parse('package:transformer_utils/transformer_utils.dart')); }); }); diff --git a/test/vm_tests/builder/vendor/transformer_utils/unit/transformed_source_file_test.dart b/test/vm_tests/builder/vendor/transformer_utils/unit/transformed_source_file_test.dart index 33e0b5fbd..ded449ec0 100644 --- a/test/vm_tests/builder/vendor/transformer_utils/unit/transformed_source_file_test.dart +++ b/test/vm_tests/builder/vendor/transformer_utils/unit/transformed_source_file_test.dart @@ -23,6 +23,8 @@ import 'package:over_react/src/builder/vendor/transformer_utils/src/transformed_ import 'package:source_span/source_span.dart'; import 'package:test/test.dart'; +import '../test_utils.dart'; + main() { group('TransformedSourceFile', () { @@ -68,7 +70,7 @@ main() { transformedFile.replace(testSourceFile.span(1, 3), '{replaced 1}'); transformedFile.replace(testSourceFile.span(0, 2), '{replaced 2}'); expect(() => transformedFile.getTransformedText(), - throwsA(startsWith('Overlapping replacement'))); + throwsA(hasToStringValue(contains('Overlapping replacement')))); }); }); From 559203bec37a5c045ef4f0b44aecc6334c9a6de0 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Fri, 13 Feb 2026 13:14:14 -0700 Subject: [PATCH 5/6] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc64abd21..4bf43c72d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # OverReact Changelog +## Unreleased +- Remove dependency on `transformer_utils` + ## 5.5.0 - [#989] Optimize generated code to decrease dart2js compile size, saving ~577 bytes per component (when using `-03 --csp --minify`) - [#992] Fix compilation errors for legacy boilerplate defined in libraries with a Dart language version of >=3.0 From 973e70cbbb08bdb20ca2f761bd9c18e16fdfb4f3 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Fri, 13 Feb 2026 13:20:13 -0700 Subject: [PATCH 6/6] Format --- .../src/analyzer_helpers.dart | 32 ++++++++----------- .../transformer_utils/src/barback_utils.dart | 7 ++-- .../transformer_utils/src/node_with_meta.dart | 10 +++--- .../transformer_utils/src/text_util.dart | 7 ++-- .../src/transformed_source_file.dart | 13 +++----- .../transformer_utils/transformer_utils.dart | 6 ++-- 6 files changed, 27 insertions(+), 48 deletions(-) diff --git a/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart b/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart index c83a1c1ad..788c1ed99 100644 --- a/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart +++ b/lib/src/builder/vendor/transformer_utils/src/analyzer_helpers.dart @@ -34,8 +34,7 @@ String copyClassMember(ClassMember? member, String body) { if (member.isSetter) return _copySetterDeclaration(member, body); return _copyMethodDeclaration(member, body); } - throw UnsupportedError( - 'Unsupported class member type: ${member.runtimeType}. ' + throw UnsupportedError('Unsupported class member type: ${member.runtimeType}. ' 'Only FieldDeclaration and MethodDeclaration are supported.'); } @@ -45,8 +44,7 @@ String copyClassMember(ClassMember? member, String body) { /// If this is being leveraged within a transformer, you can associate the /// returned [DeclarationWithMeta] instance with the asset in which it is /// located by passing in an [assetId]. -Iterable getDeclarationsAnnotatedBy( - CompilationUnit unit, Type annotation) { +Iterable getDeclarationsAnnotatedBy(CompilationUnit unit, Type annotation) { var annotationName = _getReflectedName(annotation); return unit.declarations.where((member) { return member.metadata.any((meta) => meta.name.name == annotationName); @@ -91,8 +89,8 @@ dynamic getValue(Expression expression, /// or null if no matching annotations are found. Annotation? getMatchingAnnotation(AnnotatedNode member, Type annotationType) { // Be sure to use `originalDeclaration` so that generic parameters work. - final classMirror = mirrors.reflectClass(annotationType).originalDeclaration - as mirrors.ClassMirror; + final classMirror = + mirrors.reflectClass(annotationType).originalDeclaration as mirrors.ClassMirror; String className = mirrors.MirrorSystem.getName(classMirror.simpleName); // Find the annotation that matches [type]'s name. @@ -128,19 +126,16 @@ dynamic instantiateAnnotation(AnnotatedNode member, Type annotationType, List positionalParameters = []; matchingAnnotationArgs.arguments.forEach((argument) { - var onUnsupportedExpression = onUnsupportedArgument == null - ? null - : (_) => onUnsupportedArgument(argument); + var onUnsupportedExpression = + onUnsupportedArgument == null ? null : (_) => onUnsupportedArgument(argument); if (argument is NamedExpression) { var name = argument.name.label.name; - var value = getValue(argument.expression, - onUnsupportedExpression: onUnsupportedExpression); + var value = getValue(argument.expression, onUnsupportedExpression: onUnsupportedExpression); namedParameters[Symbol(name)] = value; } else { - var value = - getValue(argument, onUnsupportedExpression: onUnsupportedExpression); + var value = getValue(argument, onUnsupportedExpression: onUnsupportedExpression); positionalParameters.add(value); } @@ -150,12 +145,12 @@ dynamic instantiateAnnotation(AnnotatedNode member, Type annotationType, String constructorName = _getConstructorName(matchingAnnotation) ?? ''; // Be sure to use `originalDeclaration` so that generic parameters work. - final classMirror = mirrors.reflectClass(annotationType).originalDeclaration - as mirrors.ClassMirror; + final classMirror = + mirrors.reflectClass(annotationType).originalDeclaration as mirrors.ClassMirror; try { - var instanceMirror = classMirror.newInstance( - Symbol(constructorName), positionalParameters, namedParameters); + var instanceMirror = + classMirror.newInstance(Symbol(constructorName), positionalParameters, namedParameters); return instanceMirror.reflectee; } catch (e, stacktrace) { throw Exception('Unable to instantiate annotation: $matchingAnnotation. This is ' @@ -232,8 +227,7 @@ String _copyMethodDeclaration(MethodDeclaration decl, String body) { /// /// Workaround for a Dart analyzer issue where the constructor name is included /// in [annotation.name]. -String _getClassName(Annotation annotation) => - annotation.name.name.split('.').first; +String _getClassName(Annotation annotation) => annotation.name.name.split('.').first; /// Returns the name of the constructor being instantiated for [annotation], or /// null if the annotation is not the invocation of a named constructor. diff --git a/lib/src/builder/vendor/transformer_utils/src/barback_utils.dart b/lib/src/builder/vendor/transformer_utils/src/barback_utils.dart index 6b0d167b3..86012a7e2 100644 --- a/lib/src/builder/vendor/transformer_utils/src/barback_utils.dart +++ b/lib/src/builder/vendor/transformer_utils/src/barback_utils.dart @@ -27,9 +27,7 @@ import 'package:source_span/source_span.dart'; /// `lib/`. Uri assetIdToPackageUri(AssetId id) { if (!id.path.startsWith('lib/')) return Uri(path: id.path); - return Uri( - scheme: 'package', - path: path.url.join(id.package, id.path.replaceFirst('lib/', ''))); + return Uri(scheme: 'package', path: path.url.join(id.package, id.path.replaceFirst('lib/', ''))); } /// Returns a [SourceSpan] spanning from the beginning to the end of the given @@ -38,8 +36,7 @@ Uri assetIdToPackageUri(AssetId id) { SourceSpan getSpanForNode(SourceFile sourceFile, AstNode node, {bool skipCommentAndMetadata = true}) { if (skipCommentAndMetadata && node is AnnotatedNode) { - return sourceFile.span( - node.firstTokenAfterCommentAndMetadata.offset, node.end); + return sourceFile.span(node.firstTokenAfterCommentAndMetadata.offset, node.end); } return sourceFile.span(node.offset, node.end); diff --git a/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart b/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart index f83970bfb..54c806645 100644 --- a/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart +++ b/lib/src/builder/vendor/transformer_utils/src/node_with_meta.dart @@ -43,10 +43,9 @@ class NodeWithMeta { /// Construct a [NodeWithMeta] instance from an [AnnotatedNode]. /// The original node will be available via [node]. /// The instantiated annotation of type `TMeta` will be available via [meta]. - NodeWithMeta(this.node, {this.assetId}) - : this.metaNode = getMatchingAnnotation(node, TMeta) { - this._meta = instantiateAnnotation(node, TMeta, - onUnsupportedArgument: unsupportedArguments.add) as TMeta?; + NodeWithMeta(this.node, {this.assetId}) : this.metaNode = getMatchingAnnotation(node, TMeta) { + this._meta = instantiateAnnotation(node, TMeta, onUnsupportedArgument: unsupportedArguments.add) + as TMeta?; } /// Whether this node's metadata has arguments that could not be initialized using [getValue] @@ -58,8 +57,7 @@ class NodeWithMeta { /// Throws a [StateError] if this node's metadata is incomplete. TMeta? get meta { if (isIncomplete) { - throw StateError( - 'Metadata is incomplete; unsupported arguments $unsupportedArguments. ' + throw StateError('Metadata is incomplete; unsupported arguments $unsupportedArguments. ' 'Use `potentiallyIncompleteMeta` instead.'); } return _meta; diff --git a/lib/src/builder/vendor/transformer_utils/src/text_util.dart b/lib/src/builder/vendor/transformer_utils/src/text_util.dart index a60a9c8dc..a6cbe2b69 100644 --- a/lib/src/builder/vendor/transformer_utils/src/text_util.dart +++ b/lib/src/builder/vendor/transformer_utils/src/text_util.dart @@ -21,8 +21,7 @@ library; /// /// If [content] is null, a string version of the the `null` literal will be /// returned instead. -String stringLiteral(String? content, - {bool quote = true, bool useSingleQuote = true}) { +String stringLiteral(String? content, {bool quote = true, bool useSingleQuote = true}) { // Adapted from dart.convert library's JSON encoder: // https://github.com/dart-lang/sdk/blob/1.12.0/sdk/lib/convert/json.dart#L565 // @@ -129,9 +128,7 @@ String stringLiteral(String? content, writeCharCode(hexDigit(charCode & 0xf)); break; } - } else if (charCode == quoteChar || - charCode == BACKSLASH || - charCode == DOLLAR_SIGN) { + } else if (charCode == quoteChar || charCode == BACKSLASH || charCode == DOLLAR_SIGN) { if (i > offset) writeStringSlice(content, offset, i); offset = i + 1; writeCharCode(BACKSLASH); diff --git a/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart b/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart index 252eeb82f..e8ed2d510 100644 --- a/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart +++ b/lib/src/builder/vendor/transformer_utils/src/transformed_source_file.dart @@ -75,8 +75,7 @@ class TransformedSourceFile { throw Exception('Overlapping replacement $replacement in replacements $_replacements.'); } - var unmodifiedText = - sourceFile.getText(lastEdge, replacement.span.start.offset); + var unmodifiedText = sourceFile.getText(lastEdge, replacement.span.start.offset); var removalText = replacement.span.text; var additionText = replacement.newText; @@ -102,9 +101,7 @@ class TransformedSourceFile { String getTransformedText() { StringBuffer transformedSource = StringBuffer(); - iterateReplacements( - onUnmodified: transformedSource.write, - onAddition: transformedSource.write); + iterateReplacements(onUnmodified: transformedSource.write, onAddition: transformedSource.write); return transformedSource.toString(); } @@ -156,11 +153,9 @@ class TransformedSourceFile { } } -SourceSpan getSpan(SourceFile sourceFile, AstNode node, - {bool skipCommentAndMetadata = true}) { +SourceSpan getSpan(SourceFile sourceFile, AstNode node, {bool skipCommentAndMetadata = true}) { if (skipCommentAndMetadata && node is AnnotatedNode) { - return sourceFile.span( - node.firstTokenAfterCommentAndMetadata.offset, node.end); + return sourceFile.span(node.firstTokenAfterCommentAndMetadata.offset, node.end); } return sourceFile.span(node.offset, node.end); diff --git a/lib/src/builder/vendor/transformer_utils/transformer_utils.dart b/lib/src/builder/vendor/transformer_utils/transformer_utils.dart index 1ee559cd8..3cac85dd7 100644 --- a/lib/src/builder/vendor/transformer_utils/transformer_utils.dart +++ b/lib/src/builder/vendor/transformer_utils/transformer_utils.dart @@ -18,9 +18,7 @@ library; export './src/analyzer_helpers.dart' show copyClassMember, getDeclarationsAnnotatedBy, instantiateAnnotation; -export './src/barback_utils.dart' - show assetIdToPackageUri, getSpanForNode; +export './src/barback_utils.dart' show assetIdToPackageUri, getSpanForNode; export './src/node_with_meta.dart' show NodeWithMeta; export './src/text_util.dart' show stringLiteral; -export './src/transformed_source_file.dart' - show TransformedSourceFile, getSpan; +export './src/transformed_source_file.dart' show TransformedSourceFile, getSpan;