diff --git a/lib/src/comment_references/model_comment_reference.dart b/lib/src/comment_references/model_comment_reference.dart new file mode 100644 index 0000000000..221f4ee4a4 --- /dev/null +++ b/lib/src/comment_references/model_comment_reference.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +// +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/file_system/file_system.dart'; +import 'package:dartdoc/src/model_utils.dart'; + +/// A stripped down analyzer AST [CommentReference] containing only that +/// information needed for Dartdoc. Drops link to the [CommentReference] +/// and [ResourceProvider] after construction. +class ModelCommentReference { + final String codeRef; + final Element staticElement; + + ModelCommentReference(CommentReference ref, ResourceProvider resourceProvider) + : codeRef = _referenceText(ref, resourceProvider), + staticElement = ref.identifier.staticElement; + + /// "Unparse" the code reference into the raw text associated with the + /// [CommentReference]. + static String _referenceText( + CommentReference ref, ResourceProvider resourceProvider) { + var contents = getFileContentsFor( + (ref.root as CompilationUnit).declaredElement, resourceProvider); + return contents.substring(ref.offset, ref.end); + } +} diff --git a/lib/src/generator/templates.renderers.dart b/lib/src/generator/templates.renderers.dart index 7c82c2c6c8..1650d7f121 100644 --- a/lib/src/generator/templates.renderers.dart +++ b/lib/src/generator/templates.renderers.dart @@ -614,12 +614,13 @@ class _Renderer_Canonicalization extends RendererBase { getValue: (CT_ c) => c.commentRefs, renderVariable: (CT_ c, Property self, List remainingNames) => - self.renderSimpleVariable( - c, remainingNames, 'List'), - renderIterable: + self.renderSimpleVariable(c, remainingNames, + 'Map'), + isNullValue: (CT_ c) => c.commentRefs == null, + renderValue: (CT_ c, RendererBase r, List ast) { - return c.commentRefs.map( - (e) => renderSimple(e, ast, r.template, parent: r)); + return renderSimple(c.commentRefs, ast, r.template, + parent: r); }, ), 'isCanonical': Property( @@ -902,6 +903,19 @@ class _Renderer_Category extends RendererBase { (e) => _render_Class(e, ast, r.template, parent: r)); }, ), + 'commentRefs': Property( + getValue: (CT_ c) => c.commentRefs, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, + 'Map'), + isNullValue: (CT_ c) => c.commentRefs == null, + renderValue: + (CT_ c, RendererBase r, List ast) { + return renderSimple(c.commentRefs, ast, r.template, + parent: r); + }, + ), 'config': Property( getValue: (CT_ c) => c.config, renderVariable: (CT_ c, Property self, @@ -8912,12 +8926,13 @@ class _Renderer_ModelElement extends RendererBase { getValue: (CT_ c) => c.commentRefs, renderVariable: (CT_ c, Property self, List remainingNames) => - self.renderSimpleVariable( - c, remainingNames, 'List'), - renderIterable: + self.renderSimpleVariable(c, remainingNames, + 'Map'), + isNullValue: (CT_ c) => c.commentRefs == null, + renderValue: (CT_ c, RendererBase r, List ast) { - return c.commentRefs.map( - (e) => renderSimple(e, ast, r.template, parent: r)); + return renderSimple(c.commentRefs, ast, r.template, + parent: r); }, ), 'compilationUnitElement': Property( @@ -10394,6 +10409,19 @@ class _Renderer_Package extends RendererBase { (e) => _render_Category(e, ast, r.template, parent: r)); }, ), + 'commentRefs': Property( + getValue: (CT_ c) => c.commentRefs, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, + 'Map'), + isNullValue: (CT_ c) => c.commentRefs == null, + renderValue: + (CT_ c, RendererBase r, List ast) { + return renderSimple(c.commentRefs, ast, r.template, + parent: r); + }, + ), 'config': Property( getValue: (CT_ c) => c.config, renderVariable: (CT_ c, Property self, diff --git a/lib/src/markdown_processor.dart b/lib/src/markdown_processor.dart index 4ae5d2d440..7c6c050af7 100644 --- a/lib/src/markdown_processor.dart +++ b/lib/src/markdown_processor.dart @@ -126,10 +126,6 @@ final RegExp nonHTML = /// parentheses. final _trailingIgnorePattern = RegExp(r'(<.*>|\(.*\)|\?|!)$'); -@Deprecated('Public variable intended to be private; will be removed as early ' - 'as Dartdoc 1.0.0') -RegExp get trailingIgnoreStuff => _trailingIgnorePattern; - /// Things to ignore at the beginning of a doc reference. /// /// This is intended to catch various keywords that a developer may include in @@ -139,18 +135,10 @@ RegExp get trailingIgnoreStuff => _trailingIgnorePattern; final _leadingIgnorePattern = RegExp(r'^(const|final|var)[\s]+', multiLine: true); -@Deprecated('Public variable intended to be private; will be removed as early ' - 'as Dartdoc 1.0.0') -RegExp get leadingIgnoreStuff => _leadingIgnorePattern; - /// If found, this pattern may indicate a reference to a constructor. final _constructorIndicationPattern = RegExp(r'(^new[\s]+|\(\)$)', multiLine: true); -@Deprecated('Public variable intended to be private; will be removed as early ' - 'as Dartdoc 1.0.0') -RegExp get isConstructor => _constructorIndicationPattern; - /// A pattern indicating that text which looks like a doc reference is not /// intended to be. /// @@ -288,8 +276,8 @@ ModelElement _getPreferredClass(ModelElement modelElement) { } /// Implements _getMatchingLinkElement via [CommentReferable.referenceBy]. -MatchingLinkResult _getMatchingLinkElementCommentReferable(String codeRef, - Warnable warnable, List commentRefs) { +MatchingLinkResult _getMatchingLinkElementCommentReferable( + String codeRef, Warnable warnable) { if (!codeRef.contains(_constructorIndicationPattern) && codeRef.contains(notARealDocReference)) { // Don't waste our time on things we won't ever find. @@ -304,8 +292,8 @@ MatchingLinkResult _getMatchingLinkElementCommentReferable(String codeRef, } /// Returns null if element is a parameter. -MatchingLinkResult _getMatchingLinkElementLegacy(String codeRef, - Warnable warnable, List commentRefs) { +MatchingLinkResult _getMatchingLinkElementLegacy( + String codeRef, Warnable warnable) { if (!codeRef.contains(_constructorIndicationPattern) && codeRef.contains(notARealDocReference)) { // Don't waste our time on things we won't ever find. @@ -322,9 +310,9 @@ MatchingLinkResult _getMatchingLinkElementLegacy(String codeRef, message: 'Comment reference resolution inside extension methods is not yet implemented'); } else { - refModelElement = _MarkdownCommentReference( - codeRef, warnable, commentRefs, preferredClass) - .computeReferredElement(); + refModelElement = + _MarkdownCommentReference(codeRef, warnable, preferredClass) + .computeReferredElement(); } } @@ -368,27 +356,28 @@ MatchingLinkResult _getMatchingLinkElementLegacy(String codeRef, return MatchingLinkResult(refModelElement); } -/// Given a set of commentRefs, return the one whose name matches the codeRef. -Element _getRefElementFromCommentRefs( - List commentRefs, String codeRef) { - if (commentRefs != null) { - for (var ref in commentRefs) { - if (ref.name == codeRef) { - var isConstrElement = ref.staticElement is ConstructorElement; - // Constructors are now handled by library search. - if (!isConstrElement) { - var refElement = ref.staticElement; - if (refElement is PropertyAccessorElement) { - // yay we found an accessor that wraps a const, but we really - // want the top-level field itself - refElement = (refElement as PropertyAccessorElement).variable; - } - if (refElement is PrefixElement) { - // We found a prefix element, but what we really want is the library element. - refElement = (refElement as PrefixElement).enclosingElement; - } - return refElement; +/// Get the element referred by the [codeRef] in analyzer. +/// Deletes constructors and otherwise messes with the output for the rest +/// of the heuristics. +Element _getRefElementFromCommentRefs(Warnable element, String codeRef) { + if (element.commentRefs != null) { + var ref = element.commentRefs[codeRef]; + // ref can be null here if the analyzer failed to recognize this as a + // comment reference (e.g. [Foo.operator []]). + if (ref != null) { + // Constructors are now handled by library search. + if (ref.staticElement is! ConstructorElement) { + var refElement = ref.staticElement; + if (refElement is PropertyAccessorElement) { + // yay we found an accessor that wraps a const, but we really + // want the top-level field itself + refElement = (refElement as PropertyAccessorElement).variable; } + if (refElement is PrefixElement) { + // We found a prefix element, but what we really want is the library element. + refElement = (refElement as PrefixElement).enclosingElement; + } + return refElement; } } } @@ -403,9 +392,6 @@ class _MarkdownCommentReference { /// The element containing the code reference. final Warnable element; - /// A list of [ModelCommentReference]s for this element. - final List commentRefs; - /// Disambiguate inheritance with this class. final Class preferredClass; @@ -421,8 +407,7 @@ class _MarkdownCommentReference { /// PackageGraph associated with this element. PackageGraph packageGraph; - _MarkdownCommentReference( - this.codeRef, this.element, this.commentRefs, this.preferredClass) { + _MarkdownCommentReference(this.codeRef, this.element, this.preferredClass) { assert(element != null); assert(element.packageGraph.allLibrariesAdded); @@ -589,7 +574,7 @@ class _MarkdownCommentReference { _codeRefChompedParts ??= codeRefChomped.split('.'); void _reducePreferAnalyzerResolution() { - var refElement = _getRefElementFromCommentRefs(commentRefs, codeRef); + var refElement = _getRefElementFromCommentRefs(element, codeRef); if (results.any((me) => me.element == refElement)) { results.removeWhere((me) => me.element != refElement); } @@ -663,8 +648,7 @@ class _MarkdownCommentReference { void _findWithoutLeadingIgnoreStuff() { if (codeRef.contains(_leadingIgnorePattern)) { var newCodeRef = codeRef.replaceFirst(_leadingIgnorePattern, ''); - results.add(_MarkdownCommentReference( - newCodeRef, element, commentRefs, preferredClass) + results.add(_MarkdownCommentReference(newCodeRef, element, preferredClass) .computeReferredElement()); } } @@ -672,8 +656,7 @@ class _MarkdownCommentReference { void _findWithoutTrailingIgnoreStuff() { if (codeRef.contains(_trailingIgnorePattern)) { var newCodeRef = codeRef.replaceFirst(_trailingIgnorePattern, ''); - results.add(_MarkdownCommentReference( - newCodeRef, element, commentRefs, preferredClass) + results.add(_MarkdownCommentReference(newCodeRef, element, preferredClass) .computeReferredElement()); } } @@ -681,8 +664,7 @@ class _MarkdownCommentReference { void _findWithoutOperatorPrefix() { if (codeRef.startsWith(operatorPrefix)) { var newCodeRef = codeRef.replaceFirst(operatorPrefix, ''); - results.add(_MarkdownCommentReference( - newCodeRef, element, commentRefs, preferredClass) + results.add(_MarkdownCommentReference(newCodeRef, element, preferredClass) .computeReferredElement()); } } @@ -826,7 +808,7 @@ class _MarkdownCommentReference { } void _findAnalyzerReferences() { - var refElement = _getRefElementFromCommentRefs(commentRefs, codeRef); + var refElement = _getRefElementFromCommentRefs(element, codeRef); if (refElement == null) return; ModelElement refModelElement; @@ -943,9 +925,8 @@ const _referenceLookupWarnings = { PackageWarning.referenceLookupMissingWithNew, }; -md.Node _makeLinkNode(String codeRef, Warnable warnable, - List commentRefs) { - var result = _getMatchingLinkElement(warnable, codeRef, commentRefs); +md.Node _makeLinkNode(String codeRef, Warnable warnable) { + var result = _getMatchingLinkElement(warnable, codeRef); var textContent = htmlEscape.convert(codeRef); var linkedElement = result.modelElement; if (linkedElement != null) { @@ -974,8 +955,7 @@ md.Node _makeLinkNode(String codeRef, Warnable warnable, return md.Element.text('code', textContent); } -MatchingLinkResult _getMatchingLinkElement(Warnable warnable, String codeRef, - List commentRefs) { +MatchingLinkResult _getMatchingLinkElement(Warnable warnable, String codeRef) { MatchingLinkResult result, resultOld, resultNew; // Do a comparison between result types only if the warnings for them are // enabled, because there's a significant performance penalty. @@ -983,9 +963,8 @@ MatchingLinkResult _getMatchingLinkElement(Warnable warnable, String codeRef, .where((entry) => _referenceLookupWarnings.contains(entry.key)) .any((entry) => entry.value != PackageWarningMode.ignore); if (doComparison) { - resultNew = - _getMatchingLinkElementCommentReferable(codeRef, warnable, commentRefs); - resultOld = _getMatchingLinkElementLegacy(codeRef, warnable, commentRefs); + resultNew = _getMatchingLinkElementCommentReferable(codeRef, warnable); + resultOld = _getMatchingLinkElementLegacy(codeRef, warnable); if (resultNew.modelElement != null) { markdownStats.resolvedNewLookupReferences++; } @@ -996,10 +975,9 @@ MatchingLinkResult _getMatchingLinkElement(Warnable warnable, String codeRef, } } else { if (warnable.config.experimentalReferenceLookup) { - result = _getMatchingLinkElementCommentReferable( - codeRef, warnable, commentRefs); + result = _getMatchingLinkElementCommentReferable(codeRef, warnable); } else { - result = _getMatchingLinkElementLegacy(codeRef, warnable, commentRefs); + result = _getMatchingLinkElementLegacy(codeRef, warnable); } } if (doComparison) { @@ -1084,13 +1062,12 @@ Iterable findFreeHangingGenericsPositions(String string) sync* { } class MarkdownDocument extends md.Document { - factory MarkdownDocument.withElementLinkResolver( - Canonicalization element, List commentRefs) { + factory MarkdownDocument.withElementLinkResolver(Canonicalization element) { md.Node /*?*/ linkResolver(String name, [String /*?*/ _]) { if (name.isEmpty) { return null; } - return _makeLinkNode(name, element, commentRefs); + return _makeLinkNode(name, element); } return MarkdownDocument( diff --git a/lib/src/model/accessor.dart b/lib/src/model/accessor.dart index 24c4511d7e..cdfb01abb8 100644 --- a/lib/src/model/accessor.dart +++ b/lib/src/model/accessor.dart @@ -4,8 +4,8 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/src/dart/element/member.dart' show ExecutableMember; -import 'package:dartdoc/src/model/comment_referable.dart'; import 'package:dartdoc/src/element_type.dart'; +import 'package:dartdoc/src/model/comment_referable.dart'; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/render/source_code_renderer.dart'; import 'package:dartdoc/src/utils.dart'; diff --git a/lib/src/model/canonicalization.dart b/lib/src/model/canonicalization.dart index 658cfc3c6a..b22a9e6e9c 100644 --- a/lib/src/model/canonicalization.dart +++ b/lib/src/model/canonicalization.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'package:dartdoc/src/comment_references/model_comment_reference.dart'; import 'package:dartdoc/src/model/model.dart'; /// Classes extending this class have canonicalization support in Dartdoc. @@ -10,7 +11,12 @@ abstract class Canonicalization implements Locatable, Documentable { Library get canonicalLibrary; - List get commentRefs => null; + /// A map of [ModelCommentReference.codeRef] to [ModelCommentReference]. + /// This map deduplicates comment references as all identical reference + /// strings inside a single documentation comment will point to the same + /// place, so it should not be used to count exactly how many references + /// there are. + Map get commentRefs; /// Pieces of the location, split to remove 'package:' and slashes. Set get locationPieces; diff --git a/lib/src/model/category.dart b/lib/src/model/category.dart index 17fcea0eba..02e3d96a7a 100644 --- a/lib/src/model/category.dart +++ b/lib/src/model/category.dart @@ -4,6 +4,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/file_system/file_system.dart'; +import 'package:dartdoc/src/comment_references/model_comment_reference.dart'; import 'package:dartdoc/src/dartdoc_options.dart'; import 'package:dartdoc/src/model/comment_referable.dart'; import 'package:dartdoc/src/model/model.dart'; @@ -220,4 +221,9 @@ class Category extends Nameable @override // TODO: implement referenceParents Iterable get referenceParents => []; + + @override + // Categories are not analyzed by the analyzer, so they can't have + // comment references. + Map get commentRefs => {}; } diff --git a/lib/src/model/comment_referable.dart b/lib/src/model/comment_referable.dart index 0975d79d44..800516d0d6 100644 --- a/lib/src/model/comment_referable.dart +++ b/lib/src/model/comment_referable.dart @@ -32,7 +32,7 @@ mixin CommentReferable implements Nameable { } CommentReferable result; - /// Search for the reference + /// Search for the reference. for (var referenceLookup in childLookups(reference)) { if (referenceChildren.containsKey(referenceLookup.lookup)) { result = referenceChildren[referenceLookup.lookup]; diff --git a/lib/src/model/documentation.dart b/lib/src/model/documentation.dart index 83461ace4b..44dc46d891 100644 --- a/lib/src/model/documentation.dart +++ b/lib/src/model/documentation.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'package:dartdoc/src/comment_references/model_comment_reference.dart'; import 'package:dartdoc/src/markdown_processor.dart'; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/render/documentation_renderer.dart'; @@ -41,7 +42,7 @@ class Documentation { return _asOneLiner; } - List get commentRefs => _element.commentRefs; + Map get commentRefs => _element.commentRefs; void _renderDocumentation(bool processFullDocs) { var parseResult = _parseDocumentation(processFullDocs); @@ -68,8 +69,7 @@ class Documentation { return DocumentationParseResult.empty; } showWarningsForGenericsOutsideSquareBracketsBlocks(text, _element); - var document = - MarkdownDocument.withElementLinkResolver(_element, commentRefs); + var document = MarkdownDocument.withElementLinkResolver(_element); return document.parseMarkdownText(text, processFullDocs); } diff --git a/lib/src/model/getter_setter_combo.dart b/lib/src/model/getter_setter_combo.dart index 6d8014f170..8e7247c93d 100644 --- a/lib/src/model/getter_setter_combo.dart +++ b/lib/src/model/getter_setter_combo.dart @@ -9,9 +9,9 @@ import 'package:analyzer/dart/ast/ast.dart' import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/source/line_info.dart'; import 'package:analyzer/src/dart/element/element.dart'; -import 'package:dartdoc/src/model/comment_referable.dart'; import 'package:dartdoc/src/element_type.dart'; import 'package:dartdoc/src/model/annotation.dart'; +import 'package:dartdoc/src/model/comment_referable.dart'; import 'package:dartdoc/src/model/feature.dart'; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/utils.dart'; diff --git a/lib/src/model/method.dart b/lib/src/model/method.dart index bd8fe1c045..2b6e38e476 100644 --- a/lib/src/model/method.dart +++ b/lib/src/model/method.dart @@ -4,9 +4,9 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/source/line_info.dart'; -import 'package:dartdoc/src/model/comment_referable.dart'; import 'package:analyzer/src/dart/element/member.dart' show ExecutableMember; import 'package:dartdoc/src/element_type.dart'; +import 'package:dartdoc/src/model/comment_referable.dart'; import 'package:dartdoc/src/model/feature.dart'; import 'package:dartdoc/src/model/model.dart'; diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart index ef277b0f9b..fb4b9ce737 100644 --- a/lib/src/model/model_element.dart +++ b/lib/src/model/model_element.dart @@ -14,9 +14,10 @@ import 'package:analyzer/src/dart/element/element.dart'; import 'package:analyzer/src/dart/element/member.dart' show ExecutableMember, Member; import 'package:collection/collection.dart'; +import 'package:dartdoc/src/comment_references/model_comment_reference.dart'; import 'package:dartdoc/src/dartdoc_options.dart'; -import 'package:dartdoc/src/model/comment_referable.dart'; import 'package:dartdoc/src/model/annotation.dart'; +import 'package:dartdoc/src/model/comment_referable.dart'; import 'package:dartdoc/src/model/feature.dart'; import 'package:dartdoc/src/model/feature_set.dart'; import 'package:dartdoc/src/model/model.dart'; @@ -405,19 +406,19 @@ abstract class ModelElement extends Canonicalization return _isPublic; } - List _commentRefs; - + Map _commentRefs; @override - List get commentRefs { + Map get commentRefs { if (_commentRefs == null) { - _commentRefs = []; + _commentRefs = {}; for (var from in documentationFrom) { var checkReferences = [from]; if (from is Accessor) { checkReferences.add(from.enclosingCombo); } for (var e in checkReferences) { - _commentRefs.addAll(e.modelNode.commentRefs ?? []); + _commentRefs + .addAll({for (var r in e.modelNode.commentRefs) r.codeRef: r}); } } } diff --git a/lib/src/model/model_node.dart b/lib/src/model/model_node.dart index 45a3044c2e..af9e9882eb 100644 --- a/lib/src/model/model_node.dart +++ b/lib/src/model/model_node.dart @@ -5,19 +5,9 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/file_system/file_system.dart'; +import 'package:dartdoc/src/comment_references/model_comment_reference.dart'; import 'package:dartdoc/src/model_utils.dart' as model_utils; -/// A stripped down [CommentReference] containing only that information needed -/// for Dartdoc. Drops link to the [CommentReference] after construction. -class ModelCommentReference { - final String name; - final Element staticElement; - - ModelCommentReference(CommentReference ref) - : name = ref.identifier.name, - staticElement = ref.identifier.staticElement; -} - /// Stripped down information derived from [AstNode] containing only information /// needed for Dartdoc. Drops link to the [AstNode] after construction. class ModelNode { @@ -29,16 +19,18 @@ class ModelNode { ModelNode(AstNode sourceNode, this.element, this.resourceProvider) : _sourceNode = sourceNode, - commentRefs = _commentRefsFor(sourceNode); + commentRefs = _commentRefsFor(sourceNode, resourceProvider); - static List _commentRefsFor(AstNode node) { + static List _commentRefsFor( + AstNode node, ResourceProvider resourceProvider) { if (node is AnnotatedNode && node?.documentationComment?.references != null) { - return node.documentationComment.references - .map((c) => ModelCommentReference(c)) - .toList(growable: false); + return [ + for (var m in node.documentationComment.references) + ModelCommentReference(m, resourceProvider), + ]; } - return null; + return []; } String _sourceCode; diff --git a/lib/src/model/package.dart b/lib/src/model/package.dart index 7e994c7c9a..4bb55857fa 100644 --- a/lib/src/model/package.dart +++ b/lib/src/model/package.dart @@ -4,6 +4,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/file_system/file_system.dart'; +import 'package:dartdoc/src/comment_references/model_comment_reference.dart'; import 'package:dartdoc/src/dartdoc_options.dart'; import 'package:dartdoc/src/io_utils.dart'; import 'package:dartdoc/src/model/comment_referable.dart'; @@ -415,4 +416,9 @@ class Package extends LibraryContainer Iterable get referenceParents => [packageGraph]; path.Context get _pathContext => _packageGraph.resourceProvider.pathContext; + + @override + // Packages are not interpreted by the analyzer in such a way to generate + // [CommentReference] nodes, so this is always empty. + Map get commentRefs => {}; }