| // 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/ast/token.dart'; | |
| import 'package:scrape/scrape.dart'; | |
| enum ArgumentMatch { | |
| noArguments, | |
| none, | |
| all, | |
| some, | |
| prefix, | |
| suffix, | |
| middle, | |
| noncontiguous, | |
| } | |
| extension on ArgumentMatch { | |
| String get description { | |
| switch (this) { | |
| case ArgumentMatch.noArguments: | |
| return 'No arguments to match'; | |
| case ArgumentMatch.none: | |
| return 'Matched none'; | |
| case ArgumentMatch.all: | |
| return 'Matched all'; | |
| case ArgumentMatch.some: | |
| return 'Matched some'; | |
| case ArgumentMatch.prefix: | |
| return 'Matched prefix'; | |
| case ArgumentMatch.suffix: | |
| return 'Matched suffix'; | |
| case ArgumentMatch.middle: | |
| return 'Matched middle'; | |
| case ArgumentMatch.noncontiguous: | |
| return 'Matched noncontiguous'; | |
| } | |
| } | |
| } | |
| void main(List<String> arguments) { | |
| Scrape() | |
| ..addHistogram('Potential use') | |
| ..addHistogram('Individual arguments') | |
| ..addHistogram('Named arguments') | |
| ..addHistogram('Positional arguments') | |
| ..addHistogram('Argument pattern') | |
| ..addHistogram('Append super args') | |
| ..addHistogram('Prepend super args') | |
| ..addHistogram('Insert super args') | |
| ..addHistogram('Do not merge super args') | |
| ..addHistogram('No explicit super(), call unnamed') | |
| ..addHistogram('No explicit super(), call same name') | |
| ..addVisitor(SuperclassParameterVisitor.new) | |
| ..runCommandLine(arguments); | |
| } | |
| class SuperclassParameterVisitor extends ScrapeVisitor { | |
| @override | |
| void visitConstructorDeclaration(ConstructorDeclaration node) { | |
| // Whether the constructor might benefit from the feature at all. | |
| var initializer = _findSuper(node); | |
| if (initializer == null) { | |
| record('Potential use', 'No: No initializer'); | |
| return; | |
| } | |
| if (initializer.argumentList.arguments.isEmpty) { | |
| record('Potential use', 'No: Empty super() argument list'); | |
| return; | |
| } | |
| record('Potential use', 'Yes'); | |
| // If we get here, we have a superclass constructor call with arguments. | |
| // See if any of them could use the feature. | |
| var positionalParamNames = node.parameters.parameters | |
| .where((param) => param.isPositional) | |
| .map((param) => param.name!.lexeme) | |
| .toList(); | |
| var namedParamNames = node.parameters.parameters | |
| .where((param) => param.isNamed) | |
| .map((param) => param.name!.lexeme) | |
| .toSet(); | |
| var matchedNamedArguments = 0; | |
| var unmatchedNamedArguments = 0; | |
| var lastPositionalParam = -1; | |
| var matchedIndexes = <int>[]; | |
| var unmatchedPositionalArguments = 0; | |
| var positionalArgCount = 0; | |
| for (var i = 0; i < initializer.argumentList.arguments.length; i++) { | |
| var argument = initializer.argumentList.arguments[i]; | |
| if (argument is NamedArgument) { | |
| var expression = argument.argumentExpression; | |
| if (expression is! SimpleIdentifier) { | |
| record( | |
| 'Individual arguments', | |
| 'Named argument expression is not identifier', | |
| ); | |
| unmatchedNamedArguments++; | |
| } else if (argument.name.lexeme != expression.name) { | |
| record( | |
| 'Individual arguments', | |
| 'Named argument name does not match expression name', | |
| ); | |
| unmatchedNamedArguments++; | |
| } else if (!namedParamNames.contains(expression.name)) { | |
| record( | |
| 'Individual arguments', | |
| 'Named argument does not match a parameter', | |
| ); | |
| unmatchedNamedArguments++; | |
| } else { | |
| record('Individual arguments', 'Argument matches a parameter'); | |
| matchedNamedArguments++; | |
| } | |
| } else { | |
| positionalArgCount++; | |
| if (argument is! SimpleIdentifier) { | |
| record( | |
| 'Individual arguments', | |
| 'Positional argument expression is not identifier', | |
| ); | |
| unmatchedPositionalArguments++; | |
| } else { | |
| // Start searching after the last matched positional parameter because | |
| // we don't allow reordering them. If two arguments are out of order, | |
| // that doesn't mean we can't use "super." at all, just that we can | |
| // only use it for *one* of those arguments. | |
| var index = positionalParamNames.indexOf( | |
| argument.name, | |
| lastPositionalParam, | |
| ); | |
| if (index == -1) { | |
| record( | |
| 'Individual arguments', | |
| 'Positional argument does not match a parameter', | |
| ); | |
| } else { | |
| record('Individual arguments', 'Argument matches a parameter'); | |
| lastPositionalParam = index; | |
| matchedIndexes.add(i); | |
| } | |
| } | |
| } | |
| } | |
| // Characterize the positional argument list. | |
| ArgumentMatch positionalMatch; | |
| if (unmatchedPositionalArguments == 0) { | |
| if (matchedIndexes.isEmpty) { | |
| positionalMatch = ArgumentMatch.noArguments; | |
| } else { | |
| positionalMatch = ArgumentMatch.all; | |
| } | |
| } else if (matchedIndexes.isEmpty) { | |
| positionalMatch = ArgumentMatch.none; | |
| } else { | |
| // If there is any unmatched argument before a matched one, then the | |
| // matched arguments are not all at the beginning. | |
| var matchedArePrefix = true; | |
| for (var i = 1; i < positionalArgCount; i++) { | |
| if (!matchedIndexes.contains(i - 1) && matchedIndexes.contains(i)) { | |
| matchedArePrefix = false; | |
| break; | |
| } | |
| } | |
| // If there is any unmatched argument after a matched one, then the | |
| // matched arguments are not all at the end. | |
| var matchedAreSuffix = true; | |
| for (var i = 0; i < positionalArgCount - 1; i++) { | |
| if (!matchedIndexes.contains(i + 1) && matchedIndexes.contains(i)) { | |
| matchedAreSuffix = false; | |
| break; | |
| } | |
| } | |
| // If any index between the first and last matched arg is not matched, | |
| // then the arguments are not contiguous. | |
| var matchedAreContiguous = true; | |
| if (matchedIndexes.isNotEmpty) { | |
| for (var i = matchedIndexes.first; i <= matchedIndexes.last; i++) { | |
| if (!matchedIndexes.contains(i)) { | |
| matchedAreContiguous = false; | |
| break; | |
| } | |
| } | |
| } | |
| if (!matchedAreContiguous) { | |
| positionalMatch = ArgumentMatch.noncontiguous; | |
| } else if (matchedArePrefix) { | |
| positionalMatch = ArgumentMatch.prefix; | |
| } else if (matchedAreSuffix) { | |
| positionalMatch = ArgumentMatch.suffix; | |
| } else { | |
| positionalMatch = ArgumentMatch.middle; | |
| } | |
| } | |
| record('Positional arguments', positionalMatch.description); | |
| // Characterize the named argument list. | |
| ArgumentMatch namedMatch; | |
| if (matchedNamedArguments == 0) { | |
| if (unmatchedNamedArguments == 0) { | |
| namedMatch = ArgumentMatch.noArguments; | |
| } else { | |
| namedMatch = ArgumentMatch.none; | |
| } | |
| } else { | |
| if (unmatchedNamedArguments == 0) { | |
| namedMatch = ArgumentMatch.all; | |
| } else { | |
| namedMatch = ArgumentMatch.some; | |
| } | |
| } | |
| record('Named arguments', namedMatch.description); | |
| var pattern = [ | |
| for (var i = 0; i < positionalArgCount; i++) | |
| matchedIndexes.contains(i) ? 's' : '_', | |
| for (var i = 0; i < matchedNamedArguments; i++) ':s', | |
| for (var i = 0; i < unmatchedNamedArguments; i++) ':_', | |
| ].join(','); | |
| record('Argument pattern', '($pattern)'); | |
| // If none of the arguments could be 'super.', then none of the proposals | |
| // apply. | |
| if (matchedIndexes.isEmpty && matchedNamedArguments == 0) return; | |
| var append = true; | |
| var prepend = true; | |
| var insert = true; | |
| var noMerge = true; | |
| var allParams = true; | |
| switch (positionalMatch) { | |
| case ArgumentMatch.noArguments: | |
| case ArgumentMatch.all: | |
| // OK. | |
| break; | |
| case ArgumentMatch.none: | |
| allParams = false; | |
| break; | |
| case ArgumentMatch.some: | |
| throw Exception('Should not get some for positional args.'); | |
| case ArgumentMatch.prefix: | |
| append = false; | |
| noMerge = false; | |
| allParams = false; | |
| break; | |
| case ArgumentMatch.suffix: | |
| prepend = false; | |
| noMerge = false; | |
| allParams = false; | |
| break; | |
| case ArgumentMatch.middle: | |
| append = false; | |
| prepend = false; | |
| noMerge = false; | |
| allParams = false; | |
| break; | |
| case ArgumentMatch.noncontiguous: | |
| append = false; | |
| prepend = false; | |
| insert = false; | |
| noMerge = false; | |
| allParams = false; | |
| break; | |
| } | |
| switch (namedMatch) { | |
| case ArgumentMatch.noArguments: | |
| case ArgumentMatch.all: | |
| // OK. | |
| break; | |
| case ArgumentMatch.none: | |
| case ArgumentMatch.some: | |
| allParams = false; | |
| break; | |
| default: | |
| throw Exception('Unexpected match.'); | |
| } | |
| record('Append super args', append ? 'Yes' : 'No'); | |
| record('Prepend super args', prepend ? 'Yes' : 'No'); | |
| record('Insert super args', insert ? 'Yes' : 'No'); | |
| record('Do not merge super args', noMerge ? 'Yes' : 'No'); | |
| var subName = _constructorName(node.name); | |
| var superName = _constructorName(initializer.constructorName?.token); | |
| record( | |
| 'No explicit super(), call same name', | |
| (allParams && superName == subName) ? 'Yes' : 'No', | |
| ); | |
| record( | |
| 'No explicit super(), call unnamed', | |
| (allParams && superName == '(unnamed)') ? 'Yes' : 'No', | |
| ); | |
| } | |
| String _constructorName(Token? name) { | |
| if (name == null) return '(unnamed)'; | |
| return name.lexeme; | |
| } | |
| SuperConstructorInvocation? _findSuper(ConstructorDeclaration node) { | |
| for (var initializer in node.initializers) { | |
| if (initializer is SuperConstructorInvocation) { | |
| return initializer; | |
| } | |
| } | |
| return null; | |
| } | |
| } |