From b9c077b435a07d749a03b51431d638639467b118 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 18 May 2022 18:46:23 +0000 Subject: [PATCH] [analysis_server] Fix type aliases with multiple literal types in LSP TypeScript spec Change-Id: I82a8344b5e4858355757b8d4d167a305f74f3ec4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/245172 Commit-Queue: Brian Wilkerson Reviewed-by: Brian Wilkerson --- .../lib/lsp_protocol/protocol_generated.dart | 358 ++++++++++++++---- .../tool/lsp_spec/typescript_parser.dart | 50 ++- 2 files changed, 334 insertions(+), 74 deletions(-) diff --git a/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart b/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart index 299fa4e8336..4f055635f77 100644 --- a/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart +++ b/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart @@ -26552,8 +26552,16 @@ class NotebookDocumentSyncOptions implements ToJsonable { } final notebookSelectorJson = json['notebookSelector']; final notebookSelector = (notebookSelectorJson as List) - .map((item) => NotebookDocumentSyncOptionsNotebookSelector.fromJson( - item as Map)) + .map((item) => NotebookDocumentSyncOptionsNotebookSelector.canParse( + item, nullLspJsonReporter) + ? Either2.t1( + NotebookDocumentSyncOptionsNotebookSelector.fromJson( + item as Map)) + : (NotebookDocumentSyncOptionsNotebookSelector2.canParse(item, nullLspJsonReporter) + ? Either2.t2( + NotebookDocumentSyncOptionsNotebookSelector2.fromJson(item as Map)) + : (throw '''$item was not one of (NotebookDocumentSyncOptionsNotebookSelector, NotebookDocumentSyncOptionsNotebookSelector2)'''))) .toList(); final saveJson = json['save']; final save = saveJson as bool?; @@ -26564,7 +26572,9 @@ class NotebookDocumentSyncOptions implements ToJsonable { } /// The notebooks to be synced - final List notebookSelector; + final List< + Either2> notebookSelector; /// Whether save notification should be forwarded to the server. Will only be /// honored if mode === `notebook`. @@ -26572,8 +26582,7 @@ class NotebookDocumentSyncOptions implements ToJsonable { Map toJson() { var __result = {}; - __result['notebookSelector'] = - notebookSelector.map((item) => item.toJson()).toList(); + __result['notebookSelector'] = notebookSelector; if (save != null) { __result['save'] = save; } @@ -26595,10 +26604,12 @@ class NotebookDocumentSyncOptions implements ToJsonable { } if (!((notebookSelector is List && (notebookSelector.every((item) => - NotebookDocumentSyncOptionsNotebookSelector.canParse( - item, reporter)))))) { + (NotebookDocumentSyncOptionsNotebookSelector.canParse( + item, reporter) || + NotebookDocumentSyncOptionsNotebookSelector2.canParse( + item, reporter))))))) { reporter.reportError( - 'must be of type List'); + 'must be of type List>'); return false; } } finally { @@ -26628,8 +26639,12 @@ class NotebookDocumentSyncOptions implements ToJsonable { return listEqual( notebookSelector, other.notebookSelector, - (NotebookDocumentSyncOptionsNotebookSelector a, - NotebookDocumentSyncOptionsNotebookSelector b) => + (Either2 + a, + Either2 + b) => a == b) && save == other.save && true; @@ -26715,6 +26730,74 @@ class NotebookDocumentSyncOptionsCells implements ToJsonable { String toString() => jsonEncoder.convert(toJson()); } +class NotebookDocumentSyncOptionsCells2 implements ToJsonable { + static const jsonHandler = LspJsonHandler( + NotebookDocumentSyncOptionsCells2.canParse, + NotebookDocumentSyncOptionsCells2.fromJson, + ); + + NotebookDocumentSyncOptionsCells2({ + required this.language, + }); + static NotebookDocumentSyncOptionsCells2 fromJson(Map json) { + final languageJson = json['language']; + final language = languageJson as String; + return NotebookDocumentSyncOptionsCells2( + language: language, + ); + } + + final String language; + + Map toJson() { + var __result = {}; + __result['language'] = language; + return __result; + } + + static bool canParse(Object? obj, LspJsonReporter reporter) { + if (obj is Map) { + reporter.push('language'); + try { + if (!obj.containsKey('language')) { + reporter.reportError('must not be undefined'); + return false; + } + final language = obj['language']; + if (language == null) { + reporter.reportError('must not be null'); + return false; + } + if (!(language is String)) { + reporter.reportError('must be of type String'); + return false; + } + } finally { + reporter.pop(); + } + return true; + } else { + reporter.reportError('must be of type NotebookDocumentSyncOptionsCells2'); + return false; + } + } + + @override + bool operator ==(Object other) { + if (other is NotebookDocumentSyncOptionsCells2 && + other.runtimeType == NotebookDocumentSyncOptionsCells2) { + return language == other.language && true; + } + return false; + } + + @override + int get hashCode => language.hashCode; + + @override + String toString() => jsonEncoder.convert(toJson()); +} + class NotebookDocumentSyncOptionsNotebookSelector implements ToJsonable { static const jsonHandler = LspJsonHandler( NotebookDocumentSyncOptionsNotebookSelector.canParse, @@ -26722,34 +26805,33 @@ class NotebookDocumentSyncOptionsNotebookSelector implements ToJsonable { ); NotebookDocumentSyncOptionsNotebookSelector({ - required this.cells, - this.notebookDocument, + this.cells, + required this.notebookDocument, }); static NotebookDocumentSyncOptionsNotebookSelector fromJson( Map json) { final cellsJson = json['cells']; - final cells = (cellsJson as List) - .map((item) => NotebookDocumentSyncOptionsCells.fromJson( + final cells = (cellsJson as List?) + ?.map((item) => NotebookDocumentSyncOptionsCells.fromJson( item as Map)) .toList(); final notebookDocumentJson = json['notebookDocument']; - final notebookDocument = notebookDocumentJson == null - ? null - : ((NotebookDocumentFilter1.canParse(notebookDocumentJson, nullLspJsonReporter) || - NotebookDocumentFilter2.canParse( - notebookDocumentJson, nullLspJsonReporter) || - NotebookDocumentFilter3.canParse( - notebookDocumentJson, nullLspJsonReporter)) - ? Either2, String>.t1( - NotebookDocumentFilter1.canParse(notebookDocumentJson, nullLspJsonReporter) - ? Either3.t1( - NotebookDocumentFilter1.fromJson( - notebookDocumentJson as Map)) - : (NotebookDocumentFilter2.canParse(notebookDocumentJson, nullLspJsonReporter) - ? Either3.t2( - NotebookDocumentFilter2.fromJson(notebookDocumentJson as Map)) - : (NotebookDocumentFilter3.canParse(notebookDocumentJson, nullLspJsonReporter) ? Either3.t3(NotebookDocumentFilter3.fromJson(notebookDocumentJson as Map)) : (throw '''$notebookDocumentJson was not one of (NotebookDocumentFilter1, NotebookDocumentFilter2, NotebookDocumentFilter3)''')))) - : (notebookDocumentJson is String ? Either2, String>.t2(notebookDocumentJson) : (throw '''$notebookDocumentJson was not one of (Either3, String)'''))); + final notebookDocument = (NotebookDocumentFilter1.canParse( + notebookDocumentJson, nullLspJsonReporter) || + NotebookDocumentFilter2.canParse( + notebookDocumentJson, nullLspJsonReporter) || + NotebookDocumentFilter3.canParse( + notebookDocumentJson, nullLspJsonReporter)) + ? Either2, String>.t1( + NotebookDocumentFilter1.canParse(notebookDocumentJson, nullLspJsonReporter) + ? Either3.t1( + NotebookDocumentFilter1.fromJson( + notebookDocumentJson as Map)) + : (NotebookDocumentFilter2.canParse(notebookDocumentJson, nullLspJsonReporter) + ? Either3.t2( + NotebookDocumentFilter2.fromJson(notebookDocumentJson as Map)) + : (NotebookDocumentFilter3.canParse(notebookDocumentJson, nullLspJsonReporter) ? Either3.t3(NotebookDocumentFilter3.fromJson(notebookDocumentJson as Map)) : (throw '''$notebookDocumentJson was not one of (NotebookDocumentFilter1, NotebookDocumentFilter2, NotebookDocumentFilter3)''')))) + : (notebookDocumentJson is String ? Either2, String>.t2(notebookDocumentJson) : (throw '''$notebookDocumentJson was not one of (Either3, String)''')); return NotebookDocumentSyncOptionsNotebookSelector( cells: cells, notebookDocument: notebookDocument, @@ -26757,21 +26839,21 @@ class NotebookDocumentSyncOptionsNotebookSelector implements ToJsonable { } /// The cells of the matching notebook to be synced. - final List cells; + final List? cells; /// The notebook to be synced If a string value is provided it matches against /// the notebook type. '*' matches every notebook. final Either2< Either3, - String>? notebookDocument; + String> notebookDocument; Map toJson() { var __result = {}; - __result['cells'] = cells.map((item) => item.toJson()).toList(); - if (notebookDocument != null) { - __result['notebookDocument'] = notebookDocument; + if (cells != null) { + __result['cells'] = cells?.map((item) => item.toJson()).toList(); } + __result['notebookDocument'] = notebookDocument; return __result; } @@ -26779,18 +26861,12 @@ class NotebookDocumentSyncOptionsNotebookSelector implements ToJsonable { if (obj is Map) { reporter.push('cells'); try { - if (!obj.containsKey('cells')) { - reporter.reportError('must not be undefined'); - return false; - } final cells = obj['cells']; - if (cells == null) { - reporter.reportError('must not be null'); - return false; - } - if (!((cells is List && - (cells.every((item) => - NotebookDocumentSyncOptionsCells.canParse(item, reporter)))))) { + if (cells != null && + !((cells is List && + (cells.every((item) => + NotebookDocumentSyncOptionsCells.canParse( + item, reporter)))))) { reporter.reportError( 'must be of type List'); return false; @@ -26800,14 +26876,19 @@ class NotebookDocumentSyncOptionsNotebookSelector implements ToJsonable { } reporter.push('notebookDocument'); try { + if (!obj.containsKey('notebookDocument')) { + reporter.reportError('must not be undefined'); + return false; + } final notebookDocument = obj['notebookDocument']; - if (notebookDocument != null && - !(((NotebookDocumentFilter1.canParse(notebookDocument, reporter) || - NotebookDocumentFilter2.canParse( - notebookDocument, reporter) || - NotebookDocumentFilter3.canParse( - notebookDocument, reporter)) || - notebookDocument is String))) { + if (notebookDocument == null) { + reporter.reportError('must not be null'); + return false; + } + if (!(((NotebookDocumentFilter1.canParse(notebookDocument, reporter) || + NotebookDocumentFilter2.canParse(notebookDocument, reporter) || + NotebookDocumentFilter3.canParse(notebookDocument, reporter)) || + notebookDocument is String))) { reporter.reportError( 'must be of type Either2, String>'); return false; @@ -26849,6 +26930,140 @@ class NotebookDocumentSyncOptionsNotebookSelector implements ToJsonable { String toString() => jsonEncoder.convert(toJson()); } +class NotebookDocumentSyncOptionsNotebookSelector2 implements ToJsonable { + static const jsonHandler = LspJsonHandler( + NotebookDocumentSyncOptionsNotebookSelector2.canParse, + NotebookDocumentSyncOptionsNotebookSelector2.fromJson, + ); + + NotebookDocumentSyncOptionsNotebookSelector2({ + required this.cells, + this.notebookDocument, + }); + static NotebookDocumentSyncOptionsNotebookSelector2 fromJson( + Map json) { + final cellsJson = json['cells']; + final cells = (cellsJson as List) + .map((item) => NotebookDocumentSyncOptionsCells2.fromJson( + item as Map)) + .toList(); + final notebookDocumentJson = json['notebookDocument']; + final notebookDocument = notebookDocumentJson == null + ? null + : ((NotebookDocumentFilter1.canParse(notebookDocumentJson, nullLspJsonReporter) || + NotebookDocumentFilter2.canParse( + notebookDocumentJson, nullLspJsonReporter) || + NotebookDocumentFilter3.canParse( + notebookDocumentJson, nullLspJsonReporter)) + ? Either2, String>.t1( + NotebookDocumentFilter1.canParse(notebookDocumentJson, nullLspJsonReporter) + ? Either3.t1( + NotebookDocumentFilter1.fromJson( + notebookDocumentJson as Map)) + : (NotebookDocumentFilter2.canParse(notebookDocumentJson, nullLspJsonReporter) + ? Either3.t2( + NotebookDocumentFilter2.fromJson(notebookDocumentJson as Map)) + : (NotebookDocumentFilter3.canParse(notebookDocumentJson, nullLspJsonReporter) ? Either3.t3(NotebookDocumentFilter3.fromJson(notebookDocumentJson as Map)) : (throw '''$notebookDocumentJson was not one of (NotebookDocumentFilter1, NotebookDocumentFilter2, NotebookDocumentFilter3)''')))) + : (notebookDocumentJson is String ? Either2, String>.t2(notebookDocumentJson) : (throw '''$notebookDocumentJson was not one of (Either3, String)'''))); + return NotebookDocumentSyncOptionsNotebookSelector2( + cells: cells, + notebookDocument: notebookDocument, + ); + } + + /// The cells of the matching notebook to be synced. + final List cells; + + /// The notebook to be synced If a string value is provided it matches against + /// the notebook type. '*' matches every notebook. + final Either2< + Either3, + String>? notebookDocument; + + Map toJson() { + var __result = {}; + __result['cells'] = cells.map((item) => item.toJson()).toList(); + if (notebookDocument != null) { + __result['notebookDocument'] = notebookDocument; + } + return __result; + } + + static bool canParse(Object? obj, LspJsonReporter reporter) { + if (obj is Map) { + reporter.push('cells'); + try { + if (!obj.containsKey('cells')) { + reporter.reportError('must not be undefined'); + return false; + } + final cells = obj['cells']; + if (cells == null) { + reporter.reportError('must not be null'); + return false; + } + if (!((cells is List && + (cells.every((item) => NotebookDocumentSyncOptionsCells2.canParse( + item, reporter)))))) { + reporter.reportError( + 'must be of type List'); + return false; + } + } finally { + reporter.pop(); + } + reporter.push('notebookDocument'); + try { + final notebookDocument = obj['notebookDocument']; + if (notebookDocument != null && + !(((NotebookDocumentFilter1.canParse(notebookDocument, reporter) || + NotebookDocumentFilter2.canParse( + notebookDocument, reporter) || + NotebookDocumentFilter3.canParse( + notebookDocument, reporter)) || + notebookDocument is String))) { + reporter.reportError( + 'must be of type Either2, String>'); + return false; + } + } finally { + reporter.pop(); + } + return true; + } else { + reporter.reportError( + 'must be of type NotebookDocumentSyncOptionsNotebookSelector2'); + return false; + } + } + + @override + bool operator ==(Object other) { + if (other is NotebookDocumentSyncOptionsNotebookSelector2 && + other.runtimeType == NotebookDocumentSyncOptionsNotebookSelector2) { + return listEqual( + cells, + other.cells, + (NotebookDocumentSyncOptionsCells2 a, + NotebookDocumentSyncOptionsCells2 b) => + a == b) && + notebookDocument == other.notebookDocument && + true; + } + return false; + } + + @override + int get hashCode => Object.hash( + lspHashCode(cells), + notebookDocument, + ); + + @override + String toString() => jsonEncoder.convert(toJson()); +} + /// Registration options specific to a notebook. /// @since 3.17.0 class NotebookDocumentSyncRegistrationOptions @@ -26872,8 +27087,16 @@ class NotebookDocumentSyncRegistrationOptions final id = idJson as String?; final notebookSelectorJson = json['notebookSelector']; final notebookSelector = (notebookSelectorJson as List) - .map((item) => NotebookDocumentSyncOptionsNotebookSelector.fromJson( - item as Map)) + .map((item) => NotebookDocumentSyncOptionsNotebookSelector.canParse( + item, nullLspJsonReporter) + ? Either2.t1( + NotebookDocumentSyncOptionsNotebookSelector.fromJson( + item as Map)) + : (NotebookDocumentSyncOptionsNotebookSelector2.canParse(item, nullLspJsonReporter) + ? Either2.t2( + NotebookDocumentSyncOptionsNotebookSelector2.fromJson(item as Map)) + : (throw '''$item was not one of (NotebookDocumentSyncOptionsNotebookSelector, NotebookDocumentSyncOptionsNotebookSelector2)'''))) .toList(); final saveJson = json['save']; final save = saveJson as bool?; @@ -26889,7 +27112,9 @@ class NotebookDocumentSyncRegistrationOptions final String? id; /// The notebooks to be synced - final List notebookSelector; + final List< + Either2> notebookSelector; /// Whether save notification should be forwarded to the server. Will only be /// honored if mode === `notebook`. @@ -26900,8 +27125,7 @@ class NotebookDocumentSyncRegistrationOptions if (id != null) { __result['id'] = id; } - __result['notebookSelector'] = - notebookSelector.map((item) => item.toJson()).toList(); + __result['notebookSelector'] = notebookSelector; if (save != null) { __result['save'] = save; } @@ -26933,10 +27157,12 @@ class NotebookDocumentSyncRegistrationOptions } if (!((notebookSelector is List && (notebookSelector.every((item) => - NotebookDocumentSyncOptionsNotebookSelector.canParse( - item, reporter)))))) { + (NotebookDocumentSyncOptionsNotebookSelector.canParse( + item, reporter) || + NotebookDocumentSyncOptionsNotebookSelector2.canParse( + item, reporter))))))) { reporter.reportError( - 'must be of type List'); + 'must be of type List>'); return false; } } finally { @@ -26968,8 +27194,12 @@ class NotebookDocumentSyncRegistrationOptions listEqual( notebookSelector, other.notebookSelector, - (NotebookDocumentSyncOptionsNotebookSelector a, - NotebookDocumentSyncOptionsNotebookSelector b) => + (Either2 + a, + Either2 + b) => a == b) && save == other.save && true; diff --git a/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart b/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart index 6cf66db4571..d2b2eabc944 100644 --- a/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart +++ b/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart @@ -253,6 +253,10 @@ class Parser { final List _tokens; int _current = 0; final List _nodes = []; + + /// A set of names already used (or reserved) by nodes. + final Set _nodeNames = {}; + Parser(this._tokens); bool get _isAtEnd => _peek().type == TokenType.EOF; @@ -260,7 +264,7 @@ class Parser { List parse() { if (_nodes.isEmpty) { while (!_isAtEnd) { - _nodes.add(_topLevel()); + _addNode(_topLevel()); // Consume any trailing semicolons. _match([TokenType.SEMI_COLON]); } @@ -268,6 +272,13 @@ class Parser { return _nodes; } + /// Adds [node] to the current list and prevents its name from being used + /// by generated interfaces. + void _addNode(AstNode node) { + _nodeNames.add(node.name); + _nodes.add(node); + } + /// Returns the current token and moves to the next. Token _advance() => _tokenAt(_current++); @@ -425,6 +436,28 @@ class Parser { allowsNull: canBeNull, allowsUndefined: canBeUndefined); } + /// Gets an available name for a node. + /// + /// If the computed name is already used, a number will be appended to the + /// end. + String _getAvailableName(String containerName, String? fieldName) { + final name = _joinNames(containerName, fieldName ?? ''); + final requiresSuffix = fieldName == null; + // If the name has already been taken, try appending a number and try + // again. + String generatedName; + var suffixIndex = 1; + do { + if (suffixIndex > 20) { + throw 'Failed to generate an available name for $name'; + } + generatedName = + requiresSuffix || suffixIndex > 1 ? '$name$suffixIndex' : name; + suffixIndex++; + } while (_nodeNames.contains(generatedName)); + return generatedName; + } + Indexer _indexer(String containerName, Comment? leadingComment) { final indexer = _field(containerName, leadingComment); _consume(TokenType.RIGHT_BRACKET, 'Expected ]'); @@ -592,9 +625,7 @@ class Parser { if (includeUndefined) { types.add(Type.Undefined); } - var typeIndex = 0; while (true) { - typeIndex++; TypeBase type; if (_match([TokenType.LEFT_BRACE])) { // Inline interfaces. @@ -613,13 +644,8 @@ class Parser { type = MapType(indexer.indexType, indexer.valueType); } else { // Add a synthetic interface to the parsers list of nodes to represent this type. - // If we have no fieldName to base the synthetic name from, we should use - // the index of this type, for example in: - // type Foo = { [..] } | { [...] } - // we will generate Foo1 and Foo2 for the types. - final nameSuffix = fieldName ?? '$typeIndex'; - final generatedName = _joinNames(containerName, nameSuffix); - _nodes.add(InlineInterface(generatedName, members)); + final generatedName = _getAvailableName(containerName, fieldName); + _addNode(InlineInterface(generatedName, members)); // Record the type as a simple type that references this interface. type = Type.identifier(generatedName); } @@ -705,6 +731,10 @@ class Parser { TypeAlias _typeAlias(Comment? leadingComment) { final name = _consume(TokenType.IDENTIFIER, 'Expected identifier'); _consume(TokenType.EQUAL, 'Expected ='); + // Reserve the name for this alias before we start reading its type so that + // inline/literal types will not try to compute the same name if they do + // not have field names. + _nodeNames.add(name.lexeme); final type = _type(name.lexeme, null); if (!_isAtEnd) { _consume(TokenType.SEMI_COLON, 'Expected ;');