From fe888296e6fe7ef0f19dedaf434f26988c11b708 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sat, 6 Apr 2024 12:55:37 +0200 Subject: [PATCH 1/5] feat: don't suggest references to annotations/pipelines/schemas --- .../lsp/safe-ds-completion-provider.ts | 24 +++++++++++-- .../lsp/safe-ds.completion-provider.test.ts | 36 +++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts b/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts index 7e0cd2d82..7284b9dc7 100644 --- a/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts +++ b/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts @@ -5,7 +5,15 @@ import { CompletionItemTag, MarkupContent } from 'vscode-languageserver'; import { createMarkupContent } from '../documentation/safe-ds-comment-provider.js'; import { SafeDsDocumentationProvider } from '../documentation/safe-ds-documentation-provider.js'; import type { SafeDsAnnotations } from '../builtins/safe-ds-annotations.js'; -import { isSdsAnnotatedObject, isSdsModule, isSdsNamedType, isSdsReference } from '../generated/ast.js'; +import { + isSdsAnnotatedObject, + isSdsModule, + isSdsNamedType, + isSdsReference, + SdsAnnotation, + SdsPipeline, + SdsSchema, +} from '../generated/ast.js'; import { getPackageName } from '../helpers/nodeProperties.js'; import { isInPipelineFile, isInStubFile } from '../helpers/fileExtensions.js'; @@ -29,7 +37,9 @@ export class SafeDsCompletionProvider extends DefaultCompletionProvider { context: CompletionContext, ): Stream { this.fixReferenceInfo(refInfo); - return super.getReferenceCandidates(refInfo, context); + return super + .getReferenceCandidates(refInfo, context) + .filter((description) => this.filterReferenceCandidate(refInfo, description)); } private fixReferenceInfo(refInfo: ReferenceInfo): void { @@ -59,6 +69,16 @@ export class SafeDsCompletionProvider extends DefaultCompletionProvider { } } + private illegalNodeTypesForReferences = new Set([SdsAnnotation, SdsPipeline, SdsSchema]); + + private filterReferenceCandidate(refInfo: ReferenceInfo, description: AstNodeDescription): boolean { + if (isSdsReference(refInfo.container)) { + return !this.illegalNodeTypesForReferences.has(description.type); + } else { + return true; + } + } + protected override createReferenceCompletionItem(nodeDescription: AstNodeDescription): CompletionValueItem { const node = nodeDescription.node; diff --git a/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts b/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts index 30e509405..22f60ff3e 100644 --- a/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts +++ b/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts @@ -294,6 +294,42 @@ describe('SafeDsCompletionProvider', async () => { shouldNotContain: ['s2'], }, }, + + // Filtering by node type + { + testName: 'reference to annotation', + code: ` + annotation MyAnnotation + + pipeline myPipeline { + <|> + `, + expectedLabels: { + shouldNotContain: ['MyAnnotation'], + }, + }, + { + testName: 'reference to pipeline', + code: ` + pipeline myPipeline { + <|> + `, + expectedLabels: { + shouldNotContain: ['myPipeline'], + }, + }, + { + testName: 'reference to schema', + code: ` + schema MySchema {} + + pipeline myPipeline { + <|> + `, + expectedLabels: { + shouldNotContain: ['MySchema'], + }, + }, ]; it.each(testCases)('$testName', async ({ code, uri, expectedLabels }) => { From 7d8fecccc4d64d24da1bbf5083abe66bef994a28 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sat, 6 Apr 2024 15:27:44 +0200 Subject: [PATCH 2/5] fix: bug in fuzzy matcher The first character of the query had to match the first character of the text. Now it just has to match the first character of the text OR the first character of any word transition. --- .../src/language/lsp/safe-ds-fuzzy-matcher.ts | 33 +++++++++++++++++++ .../src/language/safe-ds-module.ts | 2 ++ .../lsp/safe-ds.completion-provider.test.ts | 15 +++++++++ 3 files changed, 50 insertions(+) create mode 100644 packages/safe-ds-lang/src/language/lsp/safe-ds-fuzzy-matcher.ts diff --git a/packages/safe-ds-lang/src/language/lsp/safe-ds-fuzzy-matcher.ts b/packages/safe-ds-lang/src/language/lsp/safe-ds-fuzzy-matcher.ts new file mode 100644 index 000000000..20ee7c53a --- /dev/null +++ b/packages/safe-ds-lang/src/language/lsp/safe-ds-fuzzy-matcher.ts @@ -0,0 +1,33 @@ +import { DefaultFuzzyMatcher } from 'langium/lsp'; + +export class SafeDsFuzzyMatcher extends DefaultFuzzyMatcher { + public override match(query: string, text: string): boolean { + // Fixes a bug in the default fuzzy matcher. Can be removed once the bug is fixed upstream. + if (query.length === 0) { + return true; + } + + // eslint-disable-next-line no-param-reassign + let matchedFirstCharacter = false; + let previous: number | undefined = undefined; + let character = 0; + const len = text.length; + for (let i = 0; i < len; i++) { + const strChar = text.charCodeAt(i); + const testChar = query.charCodeAt(character); + if (strChar === testChar || this.toUpperCharCode(strChar) === this.toUpperCharCode(testChar)) { + matchedFirstCharacter ||= + previous === undefined || // Beginning of word + this.isWordTransition(previous, strChar); + if (matchedFirstCharacter) { + character++; + } + if (character === query.length) { + return true; + } + } + previous = strChar; + } + return false; + } +} diff --git a/packages/safe-ds-lang/src/language/safe-ds-module.ts b/packages/safe-ds-lang/src/language/safe-ds-module.ts index d003c88db..0f76c9700 100644 --- a/packages/safe-ds-lang/src/language/safe-ds-module.ts +++ b/packages/safe-ds-lang/src/language/safe-ds-module.ts @@ -44,6 +44,7 @@ import { SafeDsRunner } from './runner/safe-ds-runner.js'; import { SafeDsTypeFactory } from './typing/safe-ds-type-factory.js'; import { SafeDsMarkdownGenerator } from './generation/safe-ds-markdown-generator.js'; import { SafeDsCompletionProvider } from './lsp/safe-ds-completion-provider.js'; +import { SafeDsFuzzyMatcher } from './lsp/safe-ds-fuzzy-matcher.js'; /** * Declaration of custom services - add your own service classes here. @@ -170,6 +171,7 @@ export type SafeDsSharedServices = LangiumSharedServices; export const SafeDsSharedModule: Module> = { lsp: { + FuzzyMatcher: () => new SafeDsFuzzyMatcher(), NodeKindProvider: () => new SafeDsNodeKindProvider(), }, workspace: { diff --git a/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts b/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts index 22f60ff3e..c9b230681 100644 --- a/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts +++ b/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts @@ -330,6 +330,21 @@ describe('SafeDsCompletionProvider', async () => { shouldNotContain: ['MySchema'], }, }, + + // Special cases + { + testName: 'fuzzy matching', + code: ` + annotation Annotation + annotation MyAnnotation + annotation OtherAnnotation + + @Anno<|> + `, + expectedLabels: { + shouldContain: ['Annotation', 'MyAnnotation', 'OtherAnnotation'], + }, + }, ]; it.each(testCases)('$testName', async ({ code, uri, expectedLabels }) => { From e9b175555f39f8cdc83a0d3ceb36ed7c91d675b2 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sat, 6 Apr 2024 17:17:56 +0200 Subject: [PATCH 3/5] fix: completion of type arguments --- .../src/language/lsp/safe-ds-completion-provider.ts | 10 ++++++++++ .../language/lsp/safe-ds.completion-provider.test.ts | 8 ++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts b/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts index 7284b9dc7..99d67e334 100644 --- a/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts +++ b/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts @@ -10,6 +10,7 @@ import { isSdsModule, isSdsNamedType, isSdsReference, + isSdsTypeArgument, SdsAnnotation, SdsPipeline, SdsSchema, @@ -66,6 +67,15 @@ export class SafeDsCompletionProvider extends DefaultCompletionProvider { $containerProperty: 'member', }; } + } else if (isSdsTypeArgument(refInfo.container) && refInfo.container.$containerProperty === 'typeParameter') { + const syntheticNode = refInfo.container.$container as AstNode; + if (isSdsNamedType(syntheticNode) && syntheticNode.$containerProperty === 'value') { + refInfo.container = { + ...refInfo.container, + $container: syntheticNode.$container, + $containerProperty: 'typeParameter', + }; + } } } diff --git a/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts b/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts index c9b230681..4536d921f 100644 --- a/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts +++ b/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts @@ -255,7 +255,9 @@ describe('SafeDsCompletionProvider', async () => { { testName: 'type arguments (no prefix)', code: ` - class MyClass { + class MyClass + + class OtherClass { attr a: MyClass<<|> `, expectedLabels: { @@ -265,7 +267,9 @@ describe('SafeDsCompletionProvider', async () => { { testName: 'type arguments (with prefix)', code: ` - class MyClass { + class MyClass + + class OtherClass { attr a: MyClass `, expectedLabels: { From f5d25d947363a2890748716ca4fec7b9a33d9237 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sat, 6 Apr 2024 17:29:56 +0200 Subject: [PATCH 4/5] fix: incorrect error on named type arguments --- .../other/declarations/typeParameters.ts | 3 ++- .../main.sdstest | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts b/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts index 8ea8f13d6..f06d77504 100644 --- a/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts +++ b/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts @@ -4,6 +4,7 @@ import { isSdsClass, isSdsClassMember, isSdsDeclaration, + isSdsNamedType, isSdsNamedTypeDeclaration, isSdsParameter, isSdsParameterList, @@ -93,7 +94,7 @@ export const typeParameterMustBeUsedInCorrectPosition = (services: SafeDsService AstUtils.findLocalReferences(node).forEach((it) => { const reference = it.$refNode?.astNode; - if (!reference) { + if (!reference || !isSdsNamedType(reference)) { /* c8 ignore next 2 */ return; } diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/usage of class type parameters/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/usage of class type parameters/main.sdstest index 6a5b11d7c..4ee3d3863 100644 --- a/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/usage of class type parameters/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/usage of class type parameters/main.sdstest @@ -4,37 +4,37 @@ package tests.validation.other.declarations.typeParameters.usageOfClassTypeParam // $TEST$ no error "This type parameter of a containing class cannot be used here." class MyClass(p: »T«) sub »T« { // $TEST$ no error "This type parameter of a containing class cannot be used here." - attr a: »T« + attr a1: »T« // $TEST$ error "This type parameter of a containing class cannot be used here." - static attr a: »T« + static attr a2: »T« // $TEST$ no error "This type parameter of a containing class cannot be used here." // $TEST$ no error "This type parameter of a containing class cannot be used here." // $TEST$ no error "This type parameter of a containing class cannot be used here." // $TEST$ no error "This type parameter of a containing class cannot be used here." - fun f(p1: »T«, p2: »S«) -> (r1: »T«, r2: »S«) + @Pure fun f1(p1: »T«, p2: »S«) -> (r1: »T«, r2: »S«) // $TEST$ error "This type parameter of a containing class cannot be used here." // $TEST$ no error "This type parameter of a containing class cannot be used here." // $TEST$ error "This type parameter of a containing class cannot be used here." // $TEST$ no error "This type parameter of a containing class cannot be used here." - static fun f(p1: »T«, p2: »S«) -> (r1: »T«, r2: »S«) + @Pure static fun f2(p1: »T«, p2: »S«) -> (r1: »T«, r2: »S«) // $TEST$ error "This type parameter of a containing class cannot be used here." // $TEST$ no error "This type parameter of a containing class cannot be used here." class MyInnerClass(p1: »T«, p2: »S«) { // $TEST$ error "This type parameter of a containing class cannot be used here." - attr a: »T« + attr a1: »T« // $TEST$ error "This type parameter of a containing class cannot be used here." - static attr a: »T« + static attr a2: »T« // $TEST$ error "This type parameter of a containing class cannot be used here." - fun f(p: »T«) + @Pure fun f1(p: »T«) // $TEST$ error "This type parameter of a containing class cannot be used here." - static fun f(p: »T«) + @Pure static fun f2(p: »T«) } enum MyInnerEnum { @@ -42,3 +42,8 @@ class MyClass(p: »T«) sub »T« { MyEnumVariant(p1: »T«) } } + +class MyOtherClass { + // $TEST$ no error "This type parameter of a containing class cannot be used here." + attr a: MyClass<»T« = Int> +} From 03bf3a9b8f01e76a9270b3975ad5acb8e267f37f Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sat, 6 Apr 2024 17:41:39 +0200 Subject: [PATCH 5/5] feat: don't suggest type parameters of containing class --- .../lsp/safe-ds-completion-provider.ts | 128 ++++++++++-------- .../other/declarations/typeParameters.ts | 2 +- .../lsp/safe-ds.completion-provider.test.ts | 12 ++ 3 files changed, 85 insertions(+), 57 deletions(-) diff --git a/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts b/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts index 99d67e334..bc821d47e 100644 --- a/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts +++ b/packages/safe-ds-lang/src/language/lsp/safe-ds-completion-provider.ts @@ -1,5 +1,5 @@ import { CompletionContext, CompletionValueItem, DefaultCompletionProvider } from 'langium/lsp'; -import { AstNode, AstNodeDescription, ReferenceInfo, Stream } from 'langium'; +import { AstNode, AstNodeDescription, AstUtils, ReferenceInfo, Stream } from 'langium'; import { SafeDsServices } from '../safe-ds-module.js'; import { CompletionItemTag, MarkupContent } from 'vscode-languageserver'; import { createMarkupContent } from '../documentation/safe-ds-comment-provider.js'; @@ -7,16 +7,20 @@ import { SafeDsDocumentationProvider } from '../documentation/safe-ds-documentat import type { SafeDsAnnotations } from '../builtins/safe-ds-annotations.js'; import { isSdsAnnotatedObject, + isSdsClass, + isSdsDeclaration, isSdsModule, isSdsNamedType, isSdsReference, isSdsTypeArgument, + isSdsTypeParameter, SdsAnnotation, SdsPipeline, SdsSchema, } from '../generated/ast.js'; import { getPackageName } from '../helpers/nodeProperties.js'; import { isInPipelineFile, isInStubFile } from '../helpers/fileExtensions.js'; +import { classTypeParameterIsUsedInCorrectPosition } from '../validation/other/declarations/typeParameters.js'; export class SafeDsCompletionProvider extends DefaultCompletionProvider { private readonly builtinAnnotations: SafeDsAnnotations; @@ -43,50 +47,46 @@ export class SafeDsCompletionProvider extends DefaultCompletionProvider { .filter((description) => this.filterReferenceCandidate(refInfo, description)); } - private fixReferenceInfo(refInfo: ReferenceInfo): void { - if (isSdsNamedType(refInfo.container) && refInfo.container.$containerProperty === 'declaration') { - const syntheticNode = refInfo.container.$container as AstNode; - if (isSdsNamedType(syntheticNode) && syntheticNode.$containerProperty === 'member') { - refInfo.container = { - ...refInfo.container, - $container: syntheticNode.$container, - $containerProperty: 'member', - }; - } else { - refInfo.container = { - ...refInfo.container, - $containerProperty: 'member', - }; - } - } else if (isSdsReference(refInfo.container) && refInfo.container.$containerProperty === 'member') { - const syntheticNode = refInfo.container.$container as AstNode; - if (isSdsReference(syntheticNode) && syntheticNode.$containerProperty === 'member') { - refInfo.container = { - ...refInfo.container, - $container: syntheticNode.$container, - $containerProperty: 'member', - }; - } - } else if (isSdsTypeArgument(refInfo.container) && refInfo.container.$containerProperty === 'typeParameter') { - const syntheticNode = refInfo.container.$container as AstNode; - if (isSdsNamedType(syntheticNode) && syntheticNode.$containerProperty === 'value') { - refInfo.container = { - ...refInfo.container, - $container: syntheticNode.$container, - $containerProperty: 'typeParameter', - }; + private illegalNodeTypesForReferences = new Set([SdsAnnotation, SdsPipeline, SdsSchema]); + + private filterReferenceCandidate(refInfo: ReferenceInfo, description: AstNodeDescription): boolean { + if (isSdsNamedType(refInfo.container)) { + if (isSdsTypeParameter(description.node)) { + const declarationWithTypeParameter = AstUtils.getContainerOfType( + description.node.$container, + isSdsDeclaration, + ); + + return ( + !isSdsClass(declarationWithTypeParameter) || + classTypeParameterIsUsedInCorrectPosition(declarationWithTypeParameter, refInfo.container) + ); } + } else if (isSdsReference(refInfo.container)) { + return !this.illegalNodeTypesForReferences.has(description.type); } + + return true; } - private illegalNodeTypesForReferences = new Set([SdsAnnotation, SdsPipeline, SdsSchema]); + private illegalKeywordsInPipelineFile = new Set(['annotation', 'class', 'enum', 'fun', 'schema']); + private illegalKeywordsInStubFile = new Set(['pipeline', 'internal', 'private', 'segment']); - private filterReferenceCandidate(refInfo: ReferenceInfo, description: AstNodeDescription): boolean { - if (isSdsReference(refInfo.container)) { - return !this.illegalNodeTypesForReferences.has(description.type); - } else { - return true; + protected override filterKeyword(context: CompletionContext, keyword: Keyword): boolean { + // Filter out keywords that do not contain any word character + if (!/\p{L}/u.test(keyword.value)) { + return false; } + + if ((!context.node || isSdsModule(context.node)) && !getPackageName(context.node)) { + return keyword.value === 'package'; + } else if (isSdsModule(context.node) && isInPipelineFile(context.node)) { + return !this.illegalKeywordsInPipelineFile.has(keyword.value); + } else if (isSdsModule(context.node) && isInStubFile(context.node)) { + return !this.illegalKeywordsInStubFile.has(keyword.value); + } + + return true; } protected override createReferenceCompletionItem(nodeDescription: AstNodeDescription): CompletionValueItem { @@ -119,23 +119,39 @@ export class SafeDsCompletionProvider extends DefaultCompletionProvider { } } - private illegalKeywordsInPipelineFile = new Set(['annotation', 'class', 'enum', 'fun', 'schema']); - private illegalKeywordsInStubFile = new Set(['pipeline', 'internal', 'private', 'segment']); - - protected override filterKeyword(context: CompletionContext, keyword: Keyword): boolean { - // Filter out keywords that do not contain any word character - if (!/\p{L}/u.test(keyword.value)) { - return false; - } - - if ((!context.node || isSdsModule(context.node)) && !getPackageName(context.node)) { - return keyword.value === 'package'; - } else if (isSdsModule(context.node) && isInPipelineFile(context.node)) { - return !this.illegalKeywordsInPipelineFile.has(keyword.value); - } else if (isSdsModule(context.node) && isInStubFile(context.node)) { - return !this.illegalKeywordsInStubFile.has(keyword.value); - } else { - return true; + private fixReferenceInfo(refInfo: ReferenceInfo): void { + if (isSdsNamedType(refInfo.container) && refInfo.container.$containerProperty === 'declaration') { + const syntheticNode = refInfo.container.$container as AstNode; + if (isSdsNamedType(syntheticNode) && syntheticNode.$containerProperty === 'member') { + refInfo.container = { + ...refInfo.container, + $container: syntheticNode.$container, + $containerProperty: 'member', + }; + } else { + refInfo.container = { + ...refInfo.container, + $containerProperty: 'member', + }; + } + } else if (isSdsReference(refInfo.container) && refInfo.container.$containerProperty === 'member') { + const syntheticNode = refInfo.container.$container as AstNode; + if (isSdsReference(syntheticNode) && syntheticNode.$containerProperty === 'member') { + refInfo.container = { + ...refInfo.container, + $container: syntheticNode.$container, + $containerProperty: 'member', + }; + } + } else if (isSdsTypeArgument(refInfo.container) && refInfo.container.$containerProperty === 'typeParameter') { + const syntheticNode = refInfo.container.$container as AstNode; + if (isSdsNamedType(syntheticNode) && syntheticNode.$containerProperty === 'value') { + refInfo.container = { + ...refInfo.container, + $container: syntheticNode.$container, + $containerProperty: 'typeParameter', + }; + } } } } diff --git a/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts b/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts index f06d77504..48bfbca37 100644 --- a/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts +++ b/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts @@ -146,7 +146,7 @@ const isInConstructor = (node: AstNode) => { return isSdsClass(parameterList?.$container); }; -const classTypeParameterIsUsedInCorrectPosition = (classWithTypeParameter: SdsClass, reference: AstNode) => { +export const classTypeParameterIsUsedInCorrectPosition = (classWithTypeParameter: SdsClass, reference: AstNode) => { const containingClassMember = AstUtils.getContainerOfType(reference, isSdsClassMember); // Handle usage in constructor diff --git a/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts b/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts index 4536d921f..de6b74c8e 100644 --- a/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts +++ b/packages/safe-ds-lang/tests/language/lsp/safe-ds.completion-provider.test.ts @@ -300,6 +300,18 @@ describe('SafeDsCompletionProvider', async () => { }, // Filtering by node type + { + testName: 'named type to type parameter of containing class', + code: ` + class MyClass { + class MyNestedClass(p: <|> + } + `, + expectedLabels: { + shouldContain: ['T2'], + shouldNotContain: ['T1'], + }, + }, { testName: 'reference to annotation', code: `