mirror of
https://github.com/dart-lang/sdk
synced 2024-09-19 15:01:29 +00:00
75bcd5ca33
See https://github.com/Dart-Code/Dart-Code/issues/3412. Change-Id: Icc44ff0537122d6d2211d64037666ecb58a28bd6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/203940 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
1106 lines
39 KiB
Dart
1106 lines
39 KiB
Dart
// Copyright (c) 2020, 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:analysis_server/lsp_protocol/protocol_generated.dart';
|
|
import 'package:analysis_server/src/lsp/constants.dart';
|
|
import 'package:analysis_server/src/lsp/semantic_tokens/legend.dart';
|
|
import 'package:analysis_server/src/protocol/protocol_internal.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
|
|
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import 'server_abstract.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(SemanticTokensTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class SemanticTokensTest extends AbstractLspAnalysisServerTest {
|
|
/// Decode tokens according to the LSP spec and pair with relevant file contents.
|
|
List<_Token> decodeSemanticTokens(String content, SemanticTokens tokens) {
|
|
final contentLines = content.split('\n').map((line) => '$line\n').toList();
|
|
final results = <_Token>[];
|
|
|
|
var lastLine = 0;
|
|
var lastColumn = 0;
|
|
for (var i = 0; i < tokens.data.length; i += 5) {
|
|
final lineDelta = tokens.data[i];
|
|
final columnDelta = tokens.data[i + 1];
|
|
final length = tokens.data[i + 2];
|
|
final tokenTypeIndex = tokens.data[i + 3];
|
|
final modifierBitmask = tokens.data[i + 4];
|
|
|
|
// Calculate the actual line/col from the deltas.
|
|
final line = lastLine + lineDelta;
|
|
final column = lineDelta == 0 ? lastColumn + columnDelta : columnDelta;
|
|
|
|
final tokenContent =
|
|
contentLines[line].substring(column, column + length);
|
|
results.add(_Token(
|
|
tokenContent,
|
|
semanticTokenLegend.typeForIndex(tokenTypeIndex),
|
|
semanticTokenLegend.modifiersForBitmask(modifierBitmask),
|
|
));
|
|
|
|
lastLine = line;
|
|
lastColumn = column;
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
Future<void> test_class() async {
|
|
final content = '''
|
|
/// class docs
|
|
class MyClass<T> {
|
|
// class comment
|
|
}
|
|
|
|
// Trailing comment
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('/// class docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('T', SemanticTokenTypes.typeParameter),
|
|
_Token('// class comment', SemanticTokenTypes.comment),
|
|
_Token('// Trailing comment', SemanticTokenTypes.comment),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_class_constructors() async {
|
|
final content = '''
|
|
class MyClass {
|
|
MyClass();
|
|
MyClass.named();
|
|
factory MyClass.factory() => MyClass();
|
|
}
|
|
|
|
final a = MyClass();
|
|
final b = MyClass.named();
|
|
final c = MyClass.factory();
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('named', SemanticTokenTypes.method,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('factory', SemanticTokenTypes.keyword),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('factory', SemanticTokenTypes.method,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('MyClass', SemanticTokenTypes.class_,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('final', SemanticTokenTypes.keyword),
|
|
_Token('a', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('MyClass', SemanticTokenTypes.class_,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('final', SemanticTokenTypes.keyword),
|
|
_Token('b', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('MyClass', SemanticTokenTypes.class_,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('named', SemanticTokenTypes.method,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('final', SemanticTokenTypes.keyword),
|
|
_Token('c', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('MyClass', SemanticTokenTypes.class_,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('factory', SemanticTokenTypes.method,
|
|
[CustomSemanticTokenModifiers.constructor])
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_class_fields() async {
|
|
final content = '''
|
|
class MyClass {
|
|
/// field docs
|
|
String myField = 'FieldVal';
|
|
/// static field docs
|
|
static String myStaticField = 'StaticFieldVal';
|
|
}
|
|
|
|
main() {
|
|
final a = MyClass();
|
|
print(a.myField);
|
|
MyClass.myStaticField = 'a';
|
|
}
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('/// field docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('myField', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token("'FieldVal'", SemanticTokenTypes.string),
|
|
_Token('/// static field docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('static', SemanticTokenTypes.keyword),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('myStaticField', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token("'StaticFieldVal'", SemanticTokenTypes.string),
|
|
_Token('main', SemanticTokenTypes.function,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('final', SemanticTokenTypes.keyword),
|
|
_Token('a', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('MyClass', SemanticTokenTypes.class_,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('print', SemanticTokenTypes.function),
|
|
_Token('a', SemanticTokenTypes.variable),
|
|
_Token('myField', SemanticTokenTypes.property),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('myStaticField', SemanticTokenTypes.property,
|
|
[SemanticTokenModifiers.static]),
|
|
_Token("'a'", SemanticTokenTypes.string),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_class_getterSetter() async {
|
|
final content = '''
|
|
class MyClass {
|
|
/// getter docs
|
|
String get myGetter => 'GetterVal';
|
|
/// setter docs
|
|
set mySetter(String v) {};
|
|
/// static getter docs
|
|
static String get myStaticGetter => 'StaticGetterVal';
|
|
/// static setter docs
|
|
static set myStaticSetter(String staticV) {};
|
|
}
|
|
|
|
main() {
|
|
final a = MyClass();
|
|
print(a.myGetter);
|
|
a.mySetter = 'a';
|
|
}
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('/// getter docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('get', SemanticTokenTypes.keyword),
|
|
_Token('myGetter', SemanticTokenTypes.property,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token("'GetterVal'", SemanticTokenTypes.string),
|
|
_Token('/// setter docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('set', SemanticTokenTypes.keyword),
|
|
_Token('mySetter', SemanticTokenTypes.property,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('v', SemanticTokenTypes.parameter,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('/// static getter docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('static', SemanticTokenTypes.keyword),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('get', SemanticTokenTypes.keyword),
|
|
_Token('myStaticGetter', SemanticTokenTypes.property,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token("'StaticGetterVal'", SemanticTokenTypes.string),
|
|
_Token('/// static setter docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('static', SemanticTokenTypes.keyword),
|
|
_Token('set', SemanticTokenTypes.keyword),
|
|
_Token('myStaticSetter', SemanticTokenTypes.property,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('staticV', SemanticTokenTypes.parameter,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('main', SemanticTokenTypes.function,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('final', SemanticTokenTypes.keyword),
|
|
_Token('a', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('MyClass', SemanticTokenTypes.class_,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('print', SemanticTokenTypes.function),
|
|
_Token('a', SemanticTokenTypes.variable),
|
|
_Token('myGetter', SemanticTokenTypes.property),
|
|
_Token('a', SemanticTokenTypes.variable),
|
|
_Token('mySetter', SemanticTokenTypes.property),
|
|
_Token("'a'", SemanticTokenTypes.string),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_class_method() async {
|
|
final content = '''
|
|
class MyClass {
|
|
/// method docs
|
|
@override
|
|
void myMethod() {}
|
|
/// static method docs
|
|
static void myStaticMethod() {
|
|
// static method comment
|
|
}
|
|
}
|
|
|
|
main() {
|
|
final a = MyClass();
|
|
a.myMethod();
|
|
MyClass.myStaticMethod();
|
|
}
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('/// method docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('@', CustomSemanticTokenTypes.annotation),
|
|
_Token('override', SemanticTokenTypes.property),
|
|
_Token('void', SemanticTokenTypes.keyword,
|
|
[CustomSemanticTokenModifiers.void_]),
|
|
_Token('myMethod', SemanticTokenTypes.method,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('/// static method docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('static', SemanticTokenTypes.keyword),
|
|
_Token('void', SemanticTokenTypes.keyword,
|
|
[CustomSemanticTokenModifiers.void_]),
|
|
_Token('myStaticMethod', SemanticTokenTypes.method,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('// static method comment', SemanticTokenTypes.comment),
|
|
_Token('main', SemanticTokenTypes.function,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('final', SemanticTokenTypes.keyword),
|
|
_Token('a', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('MyClass', SemanticTokenTypes.class_,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('a', SemanticTokenTypes.variable),
|
|
_Token('myMethod', SemanticTokenTypes.method),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('myStaticMethod', SemanticTokenTypes.method,
|
|
[SemanticTokenModifiers.static]),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_dartdoc() async {
|
|
final content = '''
|
|
/// before [aaa] after
|
|
class MyClass {
|
|
String aaa;
|
|
}
|
|
|
|
/// before [bbb] after
|
|
int double(int bbb) => bbb * 2;
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('/// before [', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('aaa', SemanticTokenTypes.property),
|
|
_Token('] after', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('aaa', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('/// before [', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('bbb', SemanticTokenTypes.parameter),
|
|
_Token('] after', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('int', SemanticTokenTypes.class_),
|
|
_Token('double', SemanticTokenTypes.function,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('int', SemanticTokenTypes.class_),
|
|
_Token('bbb', SemanticTokenTypes.parameter,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('bbb', SemanticTokenTypes.parameter),
|
|
_Token('2', SemanticTokenTypes.number)
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_directives() async {
|
|
final content = '''
|
|
import 'package:flutter/material.dart';
|
|
export 'package:flutter/widgets.dart';
|
|
import '../file.dart'
|
|
if (dart.library.io) 'file_io.dart'
|
|
if (dart.library.html) 'file_html.dart';
|
|
|
|
library foo;
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('import', SemanticTokenTypes.keyword),
|
|
_Token("'package:flutter/material.dart'", SemanticTokenTypes.string),
|
|
_Token('export', SemanticTokenTypes.keyword),
|
|
_Token("'package:flutter/widgets.dart'", SemanticTokenTypes.string),
|
|
_Token('import', SemanticTokenTypes.keyword),
|
|
_Token("'../file.dart'", SemanticTokenTypes.string),
|
|
_Token('if', SemanticTokenTypes.keyword,
|
|
[CustomSemanticTokenModifiers.control]),
|
|
_Token('dart', CustomSemanticTokenTypes.source),
|
|
_Token('library', CustomSemanticTokenTypes.source),
|
|
_Token('io', CustomSemanticTokenTypes.source),
|
|
_Token("'file_io.dart'", SemanticTokenTypes.string),
|
|
_Token('if', SemanticTokenTypes.keyword,
|
|
[CustomSemanticTokenModifiers.control]),
|
|
_Token('dart', CustomSemanticTokenTypes.source),
|
|
_Token('library', CustomSemanticTokenTypes.source),
|
|
_Token('html', CustomSemanticTokenTypes.source),
|
|
_Token("'file_html.dart'", SemanticTokenTypes.string),
|
|
_Token('library', SemanticTokenTypes.keyword),
|
|
_Token('foo', SemanticTokenTypes.namespace),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_extension() async {
|
|
final content = '''
|
|
extension A on String {}
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('extension', SemanticTokenTypes.keyword),
|
|
_Token('A', SemanticTokenTypes.class_),
|
|
_Token('on', SemanticTokenTypes.keyword),
|
|
_Token('String', SemanticTokenTypes.class_)
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_fromPlugin() async {
|
|
final pluginAnalyzedFilePath = join(projectFolderPath, 'lib', 'foo.foo');
|
|
final pluginAnalyzedFileUri = Uri.file(pluginAnalyzedFilePath);
|
|
final content = 'CLASS STRING VARIABLE';
|
|
|
|
final expected = [
|
|
_Token('CLASS', SemanticTokenTypes.class_),
|
|
_Token('STRING', SemanticTokenTypes.string),
|
|
_Token('VARIABLE', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(pluginAnalyzedFileUri, withoutMarkers(content));
|
|
|
|
final pluginResult = plugin.AnalysisHighlightsParams(
|
|
pluginAnalyzedFilePath,
|
|
[
|
|
plugin.HighlightRegion(plugin.HighlightRegionType.CLASS, 0, 5),
|
|
plugin.HighlightRegion(plugin.HighlightRegionType.LITERAL_STRING, 6, 6),
|
|
plugin.HighlightRegion(
|
|
plugin.HighlightRegionType.TOP_LEVEL_VARIABLE_DECLARATION, 13, 8),
|
|
],
|
|
);
|
|
configureTestPlugin(notification: pluginResult.toNotification());
|
|
|
|
final tokens = await getSemanticTokens(pluginAnalyzedFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_invalidSyntax() async {
|
|
final content = '''
|
|
/// class docs
|
|
class MyClass {
|
|
// class comment
|
|
}
|
|
|
|
this is not valid code.
|
|
|
|
/// class docs 2
|
|
class MyClass2 {
|
|
// class comment 2
|
|
}
|
|
''';
|
|
|
|
// Expect the correct tokens for the valid code before/after but don't
|
|
// check the the tokens for the invalid code as there are no concrete
|
|
// expectations for them.
|
|
final expected1 = [
|
|
_Token('/// class docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('// class comment', SemanticTokenTypes.comment),
|
|
];
|
|
final expected2 = [
|
|
_Token('/// class docs 2', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyClass2', SemanticTokenTypes.class_),
|
|
_Token('// class comment 2', SemanticTokenTypes.comment),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
|
|
// Remove the tokens between the two expected sets.
|
|
decoded.removeRange(expected1.length, decoded.length - expected2.length);
|
|
|
|
expect(decoded, equals([...expected1, ...expected2]));
|
|
}
|
|
|
|
Future<void> test_keywords() async {
|
|
// "control" keywords should be tagged with a modifier so the client
|
|
// can color them differently to other keywords.
|
|
final content = r'''
|
|
void main() async {
|
|
var a = new Object();
|
|
await null;
|
|
if (false) {
|
|
print('test');
|
|
}
|
|
}
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('void', SemanticTokenTypes.keyword,
|
|
[CustomSemanticTokenModifiers.void_]),
|
|
_Token('main', SemanticTokenTypes.function,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('async', SemanticTokenTypes.keyword,
|
|
[CustomSemanticTokenModifiers.control]),
|
|
_Token('var', SemanticTokenTypes.keyword),
|
|
_Token('a', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('new', SemanticTokenTypes.keyword),
|
|
_Token('Object', SemanticTokenTypes.class_,
|
|
[CustomSemanticTokenModifiers.constructor]),
|
|
_Token('await', SemanticTokenTypes.keyword,
|
|
[CustomSemanticTokenModifiers.control]),
|
|
_Token('null', SemanticTokenTypes.keyword),
|
|
_Token('if', SemanticTokenTypes.keyword,
|
|
[CustomSemanticTokenModifiers.control]),
|
|
_Token('false', CustomSemanticTokenTypes.boolean),
|
|
_Token('print', SemanticTokenTypes.function),
|
|
_Token("'test'", SemanticTokenTypes.string),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_lastLine_code() async {
|
|
final content = 'String var;';
|
|
|
|
final expected = [
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('var', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_lastLine_comment() async {
|
|
final content = '// Trailing comment';
|
|
|
|
final expected = [
|
|
_Token('// Trailing comment', SemanticTokenTypes.comment),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_lastLine_multilineComment() async {
|
|
final content = '''/**
|
|
* Trailing comment
|
|
*/''';
|
|
|
|
final expected = [
|
|
_Token('/**\n', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token(' * Trailing comment\n', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token(' */', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_manyBools_bug() async {
|
|
// Similar to test_manyImports_sortBug, this code triggered inconsistent tokens
|
|
// for "false" because tokens were sorted incorrectly (because both boolean and
|
|
// keyword had the same offset and length, which is all that were sorted by).
|
|
final content = '''
|
|
class MyTestClass {
|
|
/// test
|
|
/// test
|
|
bool test1 = false;
|
|
|
|
/// test
|
|
/// test
|
|
bool test2 = false;
|
|
|
|
/// test
|
|
/// test
|
|
bool test3 = false;
|
|
|
|
/// test
|
|
/// test
|
|
bool test4 = false;
|
|
|
|
/// test
|
|
/// test
|
|
bool test5 = false;
|
|
|
|
/// test
|
|
/// test
|
|
bool test6 = false;
|
|
}
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyTestClass', SemanticTokenTypes.class_),
|
|
for (var i = 1; i <= 6; i++) ...[
|
|
_Token('/// test', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('/// test', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('bool', SemanticTokenTypes.class_),
|
|
_Token('test$i', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('false', CustomSemanticTokenTypes.boolean),
|
|
],
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_manyImports_sortBug() async {
|
|
// This test is for a bug where some "import" tokens would not be highlighted
|
|
// correctly. Imports are made up of a DIRECTIVE token that spans a
|
|
// BUILT_IN ("import") and LITERAL_STRING. The original code sorted by only
|
|
// offset when handling overlapping tokens, which for certain lists (such as
|
|
// the one created for the code below) would result in the BUILTIN coming before
|
|
// the DIRECTIVE, which resulted in the DIRECTIVE overwriting it.
|
|
final content = '''
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
''';
|
|
|
|
final expected = [
|
|
for (var i = 0; i < 13; i++) ...[
|
|
_Token('import', SemanticTokenTypes.keyword),
|
|
_Token("'dart:async'", SemanticTokenTypes.string),
|
|
],
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_multilineRegions() async {
|
|
final content = '''
|
|
/**
|
|
* This is my class comment
|
|
*
|
|
* There are
|
|
* multiple lines
|
|
*/
|
|
class MyClass {}
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('/**\n', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token(' * This is my class comment\n', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token(' *\n', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token(' * There are\n', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token(' * multiple lines\n', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token(' */', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_namedArguments() async {
|
|
final content = '''
|
|
f({String a}) {
|
|
f(a: a);
|
|
}
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('f', SemanticTokenTypes.function,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('a', SemanticTokenTypes.parameter,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('f', SemanticTokenTypes.function),
|
|
_Token('a', SemanticTokenTypes.parameter,
|
|
[CustomSemanticTokenModifiers.label]),
|
|
_Token('a', SemanticTokenTypes.parameter),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_range() async {
|
|
final content = '''
|
|
/// class docs
|
|
class [[MyClass<T> {
|
|
// class comment
|
|
}]]
|
|
|
|
// Trailing comment
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('T', SemanticTokenTypes.typeParameter),
|
|
_Token('// class comment', SemanticTokenTypes.comment),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens =
|
|
await getSemanticTokensRange(mainFileUri, rangeFromMarkers(content));
|
|
final decoded = decodeSemanticTokens(withoutMarkers(content), tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_range_entireFile() async {
|
|
final content = '''[[
|
|
/// class docs
|
|
class MyClass<T> {
|
|
// class comment
|
|
}
|
|
|
|
// Trailing comment
|
|
]]''';
|
|
|
|
final expected = [
|
|
_Token('/// class docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
_Token('MyClass', SemanticTokenTypes.class_),
|
|
_Token('T', SemanticTokenTypes.typeParameter),
|
|
_Token('// class comment', SemanticTokenTypes.comment),
|
|
_Token('// Trailing comment', SemanticTokenTypes.comment),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens =
|
|
await getSemanticTokensRange(mainFileUri, rangeFromMarkers(content));
|
|
final decoded = decodeSemanticTokens(withoutMarkers(content), tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_range_multilineRegions() async {
|
|
final content = '''
|
|
/**
|
|
* This is my class comment
|
|
*
|
|
* [[There are
|
|
* multiple lines
|
|
*/
|
|
class]] MyClass {}
|
|
''';
|
|
|
|
final expected = [
|
|
_Token(' * There are\n', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token(' * multiple lines\n', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token(' */', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('class', SemanticTokenTypes.keyword),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens =
|
|
await getSemanticTokensRange(mainFileUri, rangeFromMarkers(content));
|
|
final decoded = decodeSemanticTokens(withoutMarkers(content), tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_strings() async {
|
|
final content = '''
|
|
String foo(String c) => c;
|
|
const string1 = 'test';
|
|
const string2 = 'test1 \$string1 test2 \${foo('a' + 'b')}';
|
|
const string3 = r'\$string1 \${string1.length}';
|
|
const string4 = \'\'\'
|
|
multi
|
|
line
|
|
string
|
|
\'\'\';
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('foo', SemanticTokenTypes.function,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('c', SemanticTokenTypes.parameter,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('c', SemanticTokenTypes.parameter),
|
|
|
|
_Token('const', SemanticTokenTypes.keyword),
|
|
_Token('string1', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token("'test'", SemanticTokenTypes.string),
|
|
|
|
_Token('const', SemanticTokenTypes.keyword),
|
|
_Token('string2', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token(r"'test1 ", SemanticTokenTypes.string),
|
|
_Token(r'$', CustomSemanticTokenTypes.source,
|
|
[CustomSemanticTokenModifiers.interpolation]),
|
|
_Token('string1', SemanticTokenTypes.property),
|
|
_Token(' test2 ', SemanticTokenTypes.string),
|
|
_Token(r'${', CustomSemanticTokenTypes.source,
|
|
[CustomSemanticTokenModifiers.interpolation]),
|
|
_Token('foo', SemanticTokenTypes.function),
|
|
_Token('(', CustomSemanticTokenTypes.source,
|
|
[CustomSemanticTokenModifiers.interpolation]),
|
|
_Token("'a'", SemanticTokenTypes.string),
|
|
_Token(' + ', CustomSemanticTokenTypes.source,
|
|
[CustomSemanticTokenModifiers.interpolation]),
|
|
_Token("'b'", SemanticTokenTypes.string),
|
|
_Token(')}', CustomSemanticTokenTypes.source,
|
|
[CustomSemanticTokenModifiers.interpolation]),
|
|
_Token("'", SemanticTokenTypes.string),
|
|
|
|
// string3 is raw and should be treated as a single string.
|
|
_Token('const', SemanticTokenTypes.keyword),
|
|
_Token('string3', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token(r"r'$string1 ${string1.length}'", SemanticTokenTypes.string),
|
|
_Token('const', SemanticTokenTypes.keyword),
|
|
|
|
_Token('string4', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token("'''\n", SemanticTokenTypes.string),
|
|
_Token('multi\n', SemanticTokenTypes.string),
|
|
_Token(' line\n', SemanticTokenTypes.string),
|
|
_Token(' string\n', SemanticTokenTypes.string),
|
|
_Token("'''", SemanticTokenTypes.string),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_strings_escape() async {
|
|
// The 9's in these strings are not part of the escapes (they make the
|
|
// strings too long).
|
|
final content = r'''
|
|
const string1 = 'it\'s escaped\\\n';
|
|
const string2 = 'hex \x12\x1299';
|
|
const string3 = 'unicode \u1234\u123499\u{123456}\u{12345699}';
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('const', SemanticTokenTypes.keyword),
|
|
_Token('string1', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token("'it", SemanticTokenTypes.string),
|
|
_Token(r"\'", SemanticTokenTypes.string,
|
|
[CustomSemanticTokenModifiers.escape]),
|
|
_Token('s escaped', SemanticTokenTypes.string),
|
|
_Token(r'\\', SemanticTokenTypes.string,
|
|
[CustomSemanticTokenModifiers.escape]),
|
|
_Token(r'\n', SemanticTokenTypes.string,
|
|
[CustomSemanticTokenModifiers.escape]),
|
|
_Token(r"'", SemanticTokenTypes.string),
|
|
_Token('const', SemanticTokenTypes.keyword),
|
|
_Token('string2', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token("'hex ", SemanticTokenTypes.string),
|
|
_Token(r'\x12', SemanticTokenTypes.string,
|
|
[CustomSemanticTokenModifiers.escape]),
|
|
_Token(r'\x12', SemanticTokenTypes.string,
|
|
[CustomSemanticTokenModifiers.escape]),
|
|
// The 99 is not part of the escape
|
|
_Token("99'", SemanticTokenTypes.string),
|
|
_Token('const', SemanticTokenTypes.keyword),
|
|
_Token('string3', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token("'unicode ", SemanticTokenTypes.string),
|
|
_Token(r'\u1234', SemanticTokenTypes.string,
|
|
[CustomSemanticTokenModifiers.escape]),
|
|
_Token(r'\u1234', SemanticTokenTypes.string,
|
|
[CustomSemanticTokenModifiers.escape]),
|
|
// The 99 is not part of the escape
|
|
_Token('99', SemanticTokenTypes.string),
|
|
_Token(r'\u{123456}', SemanticTokenTypes.string,
|
|
[CustomSemanticTokenModifiers.escape]),
|
|
// The 99 makes this invalid so i's not an escape
|
|
_Token(r"\u{12345699}'", SemanticTokenTypes.string),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_topLevel() async {
|
|
final content = '''
|
|
/// strings docs
|
|
const strings = <String>["test", 'test', r'test', \'''test\'''];
|
|
|
|
/// func docs
|
|
func(String a) => print(a);
|
|
|
|
/// abc docs
|
|
bool get abc => true;
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('/// strings docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('const', SemanticTokenTypes.keyword),
|
|
_Token('strings', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('"test"', SemanticTokenTypes.string),
|
|
_Token("'test'", SemanticTokenTypes.string),
|
|
_Token("r'test'", SemanticTokenTypes.string),
|
|
_Token("'''test'''", SemanticTokenTypes.string),
|
|
_Token('/// func docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('func', SemanticTokenTypes.function,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('String', SemanticTokenTypes.class_),
|
|
_Token('a', SemanticTokenTypes.parameter,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('print', SemanticTokenTypes.function),
|
|
_Token('a', SemanticTokenTypes.parameter),
|
|
_Token('/// abc docs', SemanticTokenTypes.comment,
|
|
[SemanticTokenModifiers.documentation]),
|
|
_Token('bool', SemanticTokenTypes.class_),
|
|
_Token('get', SemanticTokenTypes.keyword),
|
|
_Token('abc', SemanticTokenTypes.property,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('true', CustomSemanticTokenTypes.boolean),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
|
|
Future<void> test_unresolvedOrInvalid() async {
|
|
// Unresolved/invalid names should be marked as "source", which is used to
|
|
// mark up code the server thinks should be uncolored (without this, a
|
|
// clients other grammars would show through, losing the benefit from having
|
|
// resolved the code).
|
|
final content = '''
|
|
main() {
|
|
int a;
|
|
a.foo().bar.baz();
|
|
|
|
dynamic b;
|
|
b.foo().bar.baz();
|
|
}
|
|
''';
|
|
|
|
final expected = [
|
|
_Token('main', SemanticTokenTypes.function,
|
|
[SemanticTokenModifiers.declaration, SemanticTokenModifiers.static]),
|
|
_Token('int', SemanticTokenTypes.class_),
|
|
_Token('a', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('a', SemanticTokenTypes.variable),
|
|
_Token('foo', CustomSemanticTokenTypes.source),
|
|
_Token('bar', CustomSemanticTokenTypes.source),
|
|
_Token('baz', CustomSemanticTokenTypes.source),
|
|
_Token('dynamic', SemanticTokenTypes.type),
|
|
_Token('b', SemanticTokenTypes.variable,
|
|
[SemanticTokenModifiers.declaration]),
|
|
_Token('b', SemanticTokenTypes.variable),
|
|
_Token('foo', CustomSemanticTokenTypes.source),
|
|
_Token('bar', CustomSemanticTokenTypes.source),
|
|
_Token('baz', CustomSemanticTokenTypes.source),
|
|
];
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, withoutMarkers(content));
|
|
|
|
final tokens = await getSemanticTokens(mainFileUri);
|
|
final decoded = decodeSemanticTokens(content, tokens);
|
|
expect(decoded, equals(expected));
|
|
}
|
|
}
|
|
|
|
class _Token {
|
|
final String content;
|
|
final SemanticTokenTypes type;
|
|
final List<SemanticTokenModifiers> modifiers;
|
|
|
|
_Token(this.content, this.type, [this.modifiers = const []]);
|
|
|
|
@override
|
|
int get hashCode => content.hashCode;
|
|
|
|
@override
|
|
bool operator ==(Object o) =>
|
|
o is _Token &&
|
|
o.content == content &&
|
|
o.type == type &&
|
|
listEqual(
|
|
// Treat nulls the same as empty lists for convenience when comparing.
|
|
o.modifiers,
|
|
modifiers,
|
|
(SemanticTokenModifiers a, SemanticTokenModifiers b) => a == b);
|
|
|
|
/// Outputs a text representation of the token in the form of constructor
|
|
/// args for easy copy/pasting into tests to update expectations.
|
|
@override
|
|
String toString() {
|
|
final modifiersString = modifiers.isEmpty
|
|
? ''
|
|
: ', [${modifiers.map((m) => 'SemanticTokenModifiers.$m').join(', ')}]';
|
|
return "('$content', SemanticTokenTypes.$type$modifiersString)";
|
|
}
|
|
}
|