| // Copyright (c) 2014, 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:_fe_analyzer_shared/src/base/analyzer_public_api.dart'; | |
| import 'package:_fe_analyzer_shared/src/scanner/string_canonicalizer.dart'; | |
| import 'package:analyzer/dart/element/element.dart'; | |
| import 'package:analyzer/src/dart/ast/ast.dart'; | |
| import 'package:analyzer/src/dart/element/element.dart'; | |
| import 'package:analyzer/src/fine/requirements.dart'; | |
| import 'package:analyzer/src/generated/engine.dart'; | |
| import 'package:analyzer/src/utilities/extensions/string.dart'; | |
| /// The scope defined by a block. | |
| class BlockScope { | |
| /// Return the elements that are declared directly in the given [statements]. | |
| /// This does not include elements declared in nested blocks. | |
| static Iterable<Element> elementsInStatements( | |
| List<Statement> statements, | |
| ) sync* { | |
| int statementCount = statements.length; | |
| for (int i = 0; i < statementCount; i++) { | |
| Statement statement = statements[i]; | |
| if (statement is LabeledStatement) { | |
| statement = statement.statement; | |
| } | |
| if (statement is PatternVariableDeclarationStatementImpl) { | |
| for (var variable in statement.declaration.elements) { | |
| yield variable; | |
| } | |
| } else if (statement is VariableDeclarationStatement) { | |
| NodeList<VariableDeclaration> variables = statement.variables.variables; | |
| int variableCount = variables.length; | |
| for (int j = 0; j < variableCount; j++) { | |
| yield variables[j].declaredFragment!.element; | |
| } | |
| } else if (statement is FunctionDeclarationStatement) { | |
| yield statement.functionDeclaration.declaredFragment!.element; | |
| } | |
| } | |
| } | |
| } | |
| /// A scope in which a single label is defined. | |
| class LabelScope { | |
| /// The scope enclosing this scope. | |
| final LabelScope? _outerScope; | |
| /// The element representing the declared label. | |
| final LabelElement element; | |
| /// The AST node that is the target of a jump. | |
| final AstNode node; | |
| LabelScope(this._outerScope, this.element, this.node); | |
| /// Returns the scope that declares [targetLabel], searching this scope and | |
| /// enclosing scopes, or `null` if not found. | |
| LabelScope? lookup(String targetLabel) { | |
| if (element.name == targetLabel) { | |
| return this; | |
| } | |
| return _outerScope?.lookup(targetLabel); | |
| } | |
| } | |
| /// A mapping of identifiers to the elements represented by those identifiers. | |
| /// Namespaces are the building blocks for scopes. | |
| @AnalyzerPublicApi(message: 'exposed from elements') | |
| class Namespace { | |
| /// An empty namespace. | |
| static Namespace EMPTY = Namespace({}); | |
| /// A table mapping names that are defined in this namespace to the element | |
| /// representing the thing declared with that name. | |
| final Map<String, Element> _definedNames; | |
| /// Initialize a newly created namespace to have the [_definedNames]. | |
| Namespace(this._definedNames); | |
| /// Return a table containing the same mappings as those defined by this | |
| /// namespace. | |
| Map<String, Element> get definedNames2 => _definedNames; | |
| /// Return the element in this namespace that is available to the containing | |
| /// scope using the given name, or `null` if there is no such element. | |
| Element? get2(String name) => _definedNames[name]; | |
| /// Return the element in this namespace whose name is the result of combining | |
| /// the [prefix] and the [name], separated by a period, or `null` if there is | |
| /// no such element. | |
| Element? getPrefixed2(String prefix, String name) => null; | |
| } | |
| /// The builder used to build a namespace. Namespace builders are thread-safe | |
| /// and re-usable. | |
| class NamespaceBuilder { | |
| /// Create a namespace representing the export namespace of the given | |
| /// [export]. | |
| Namespace createExportNamespaceForDirective2(LibraryExportImpl export) { | |
| var exportedLibrary = export.exportedLibrary; | |
| if (exportedLibrary == null) { | |
| // | |
| // The exported library will be null if the URI does not reference a valid | |
| // library. | |
| // | |
| return Namespace.EMPTY; | |
| } | |
| var exportedNames = _getExportMapping(exportedLibrary); | |
| exportedNames = _applyCombinators(exportedNames, export.combinators); | |
| return Namespace(exportedNames); | |
| } | |
| /// Create a namespace representing the import namespace of the given | |
| /// [importedLibrary]. | |
| Namespace createImportNamespaceForDirective({ | |
| required LibraryElementImpl importedLibrary, | |
| required List<NamespaceCombinator> combinators, | |
| required PrefixFragment? prefix, | |
| }) { | |
| var exportedNames = _getExportMapping(importedLibrary); | |
| exportedNames = _applyCombinators(exportedNames, combinators); | |
| if (prefix?.name case var name?) { | |
| return PrefixedNamespace(name, exportedNames); | |
| } | |
| return Namespace(exportedNames); | |
| } | |
| /// Create a namespace representing the public namespace of the given | |
| /// [library]. | |
| Namespace createPublicNamespaceForLibrary(LibraryElementImpl library) { | |
| var definedNames = <String, Element>{}; | |
| for (var element in library.classes) { | |
| _addIfPublic(definedNames, element); | |
| } | |
| for (var element in library.enums) { | |
| _addIfPublic(definedNames, element); | |
| } | |
| for (var element in library.extensions) { | |
| _addIfPublic(definedNames, element); | |
| } | |
| for (var element in library.extensionTypes) { | |
| _addIfPublic(definedNames, element); | |
| } | |
| for (var element in library.getters) { | |
| _addIfPublic(definedNames, element); | |
| } | |
| for (var element in library.mixins) { | |
| _addIfPublic(definedNames, element); | |
| } | |
| for (var element in library.setters) { | |
| _addIfPublic(definedNames, element); | |
| } | |
| for (var element in library.topLevelFunctions) { | |
| _addIfPublic(definedNames, element); | |
| } | |
| for (var element in library.typeAliases) { | |
| _addIfPublic(definedNames, element); | |
| } | |
| // For libraries that import `dart:core` with a prefix, we have to add | |
| // `dynamic` to the `dart:core` [Namespace] specially. Note that this is not | |
| // true of, for instance, `Object`, because `Object` has a source definition | |
| // which is not possible for `dynamic`. | |
| if (library.isDartCore) { | |
| definedNames['dynamic'] = DynamicElementImpl.instance; | |
| definedNames['Never'] = NeverElementImpl.instance; | |
| } | |
| return Namespace(definedNames); | |
| } | |
| /// Add the given [element] to the table of [definedNames] if it has a | |
| /// publicly visible name. | |
| void _addIfPublic(Map<String, Element> definedNames, Element element) { | |
| var name = element.name; | |
| if (name != null && name.isNotEmpty && !name.isPrivateName) { | |
| definedNames[name] = element; | |
| } | |
| } | |
| /// Apply the given [combinators] to all of the names in the given table of | |
| /// [definedNames]. | |
| Map<String, Element> _applyCombinators( | |
| Map<String, Element> definedNames, | |
| List<NamespaceCombinator> combinators, | |
| ) { | |
| for (NamespaceCombinator combinator in combinators) { | |
| if (combinator is HideElementCombinator) { | |
| definedNames = _hide(definedNames, combinator.hiddenNames); | |
| } else if (combinator is ShowElementCombinator) { | |
| definedNames = _show(definedNames, combinator.shownNames); | |
| } else { | |
| // Internal error. | |
| AnalysisEngine.instance.instrumentationService.logError( | |
| "Unknown type of combinator: ${combinator.runtimeType}", | |
| ); | |
| } | |
| } | |
| return definedNames; | |
| } | |
| Map<String, Element> _getExportMapping(LibraryElementImpl library) { | |
| return library.exportNamespace.definedNames2; | |
| } | |
| /// Return a new map of names which has all the names from [definedNames] | |
| /// with exception of [hiddenNames]. | |
| Map<String, Element> _hide( | |
| Map<String, Element> definedNames, | |
| List<String> hiddenNames, | |
| ) { | |
| var newNames = {...definedNames}; | |
| for (String name in hiddenNames) { | |
| newNames.remove(name); | |
| newNames.remove("$name="); | |
| } | |
| return newNames; | |
| } | |
| /// Return a new map of names which has only [shownNames] from [definedNames]. | |
| Map<String, Element> _show( | |
| Map<String, Element> definedNames, | |
| List<String> shownNames, | |
| ) { | |
| var newNames = <String, Element>{}; | |
| for (String name in shownNames) { | |
| var element = definedNames[name]; | |
| if (element != null) { | |
| newNames[name] = element; | |
| } | |
| String setterName = considerCanonicalizeString("$name="); | |
| element = definedNames[setterName]; | |
| if (element != null) { | |
| newNames[setterName] = element; | |
| } | |
| } | |
| return newNames; | |
| } | |
| } | |
| /// A mapping of identifiers to the elements represented by those identifiers. | |
| /// Namespaces are the building blocks for scopes. | |
| class PrefixedNamespace implements Namespace { | |
| /// The prefix that is prepended to each of the defined names. | |
| final String _prefix; | |
| /// The length of the prefix. | |
| final int _length; | |
| /// A table mapping names that are defined in this namespace to the element | |
| /// representing the thing declared with that name. | |
| @override | |
| final Map<String, Element> _definedNames; | |
| /// Initialize a newly created namespace to have the names resulting from | |
| /// prefixing each of the [_definedNames] with the given [_prefix] (and a | |
| /// period). | |
| PrefixedNamespace(String prefix, this._definedNames) | |
| : _prefix = prefix, | |
| _length = prefix.length; | |
| @override | |
| Map<String, Element> get definedNames2 { | |
| return _definedNames; | |
| } | |
| @override | |
| Element? get2(String name) { | |
| if (name.length > _length && name.startsWith(_prefix)) { | |
| if (name.codeUnitAt(_length) == '.'.codeUnitAt(0)) { | |
| return _definedNames[name.substring(_length + 1)]; | |
| } | |
| } | |
| return null; | |
| } | |
| @override | |
| Element? getPrefixed2(String prefix, String name) { | |
| if (prefix == _prefix) { | |
| return _definedNames[name]; | |
| } | |
| return null; | |
| } | |
| } | |
| class RecordingExportNamespace implements Namespace { | |
| final LibraryElementImpl owner; | |
| final Namespace base; | |
| RecordingExportNamespace({required this.owner, required this.base}); | |
| @override | |
| Map<String, Element> get definedNames2 { | |
| globalResultRequirements?.record_library_allExportedTopLevels( | |
| element: owner, | |
| ); | |
| return base.definedNames2; | |
| } | |
| @override | |
| Map<String, Element> get _definedNames => throw UnimplementedError(); | |
| @override | |
| Element? get2(String name) { | |
| globalResultRequirements?.record_library_exportScope_get( | |
| element: owner, | |
| name: name, | |
| ); | |
| return base.get2(name); | |
| } | |
| @override | |
| Element? getPrefixed2(String prefix, String name) { | |
| return base.getPrefixed2(prefix, name); | |
| } | |
| } | |
| /// The context of targets for unlabeled `break` and `continue` statements. | |
| class UnlabeledBreakContinueContext { | |
| /// The start of the context in a function body. | |
| static const root = UnlabeledBreakContinueContext._(null, null); | |
| /// The previous context, or `null` for a function body. | |
| final UnlabeledBreakContinueContext? previous; | |
| /// The target statement, or `null` for a function body. | |
| final Statement? statement; | |
| const UnlabeledBreakContinueContext._(this.previous, this.statement); | |
| /// The target of an unlabeled `break`, or `null` if there is none. | |
| Statement? get breakTarget => statement; | |
| /// The target of an unlabeled `continue`, or `null` if there is none. | |
| Statement? get continueTarget { | |
| if (statement is SwitchStatement) { | |
| return previous?.continueTarget; | |
| } | |
| return statement; | |
| } | |
| /// Return a context representing [statement] nested within this context. | |
| UnlabeledBreakContinueContext nest(Statement statement) => | |
| UnlabeledBreakContinueContext._(this, statement); | |
| } |