Skip to content

Commit fe0c8d5

Browse files
feat: document symbol provider (#659)
### Summary of Changes Implement a document symbol provider. Compared to the default provider, it sets proper symbol kinds, adds details for functions and segments to show their signature, and marks symbols as deprecated. --------- Co-authored-by: megalinter-bot <[email protected]>
1 parent 9ba9d20 commit fe0c8d5

File tree

84 files changed

+847
-399
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+847
-399
lines changed

package-lock.json

Lines changed: 222 additions & 224 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
}
4242
],
4343
"engines": {
44-
"vscode": "^1.82.0"
44+
"vscode": "^1.83.0"
4545
},
4646
"contributes": {
4747
"languages": [
@@ -103,25 +103,23 @@
103103
"dependencies": {
104104
"chalk": "^5.3.0",
105105
"chevrotain": "^11.0.3",
106-
"commander": "^11.0.0",
106+
"commander": "^11.1.0",
107107
"glob": "^10.3.10",
108108
"langium": "^2.0.2",
109109
"radash": "^11.0.0",
110110
"true-myth": "^7.1.0",
111111
"vscode-languageclient": "^9.0.1",
112-
"vscode-languageserver": "^9.0.1",
113-
"vscode-languageserver-types": "^3.17.5",
114-
"vscode-uri": "^3.0.7"
112+
"vscode-languageserver": "^9.0.1"
115113
},
116114
"devDependencies": {
117-
"@lars-reimann/eslint-config": "^5.1.3",
115+
"@lars-reimann/eslint-config": "^5.1.4",
118116
"@lars-reimann/prettier-config": "^5.0.0",
119-
"@types/node": "^18.18.1",
120-
"@types/vscode": "^1.82.0",
117+
"@types/node": "^18.18.6",
118+
"@types/vscode": "^1.83.1",
121119
"@vitest/coverage-v8": "^0.34.6",
122120
"@vitest/ui": "^0.34.6",
123-
"concurrently": "^8.2.1",
124-
"esbuild": "^0.19.4",
121+
"concurrently": "^8.2.2",
122+
"esbuild": "^0.19.5",
125123
"esbuild-plugin-copy": "^2.1.1",
126124
"langium-cli": "^2.0.1",
127125
"typescript": "^5.2.2",
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { AstNode, DefaultDocumentSymbolProvider, LangiumDocument } from 'langium';
2+
import { DocumentSymbol, SymbolTag } from 'vscode-languageserver';
3+
import { SafeDsServices } from '../safe-ds-module.js';
4+
import { SafeDsAnnotations } from '../builtins/safe-ds-annotations.js';
5+
import {
6+
isSdsAnnotatedObject,
7+
isSdsAnnotation,
8+
isSdsAttribute,
9+
isSdsClass,
10+
isSdsEnumVariant,
11+
isSdsFunction,
12+
isSdsPipeline,
13+
isSdsSegment,
14+
} from '../generated/ast.js';
15+
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
16+
17+
export class SafeDsDocumentSymbolProvider extends DefaultDocumentSymbolProvider {
18+
private readonly builtinAnnotations: SafeDsAnnotations;
19+
private readonly typeComputer: SafeDsTypeComputer;
20+
21+
constructor(services: SafeDsServices) {
22+
super(services);
23+
24+
this.builtinAnnotations = services.builtins.Annotations;
25+
this.typeComputer = services.types.TypeComputer;
26+
}
27+
28+
protected override getSymbol(document: LangiumDocument, node: AstNode): DocumentSymbol[] {
29+
const cstNode = node.$cstNode;
30+
const nameNode = this.nameProvider.getNameNode(node);
31+
if (nameNode && cstNode) {
32+
const name = this.nameProvider.getName(node);
33+
return [
34+
{
35+
name: name ?? nameNode.text,
36+
kind: this.nodeKindProvider.getSymbolKind(node),
37+
tags: this.getTags(node),
38+
detail: this.getDetails(node),
39+
range: cstNode.range,
40+
selectionRange: nameNode.range,
41+
children: this.getChildSymbols(document, node),
42+
},
43+
];
44+
} else {
45+
return this.getChildSymbols(document, node) || [];
46+
}
47+
}
48+
49+
protected override getChildSymbols(document: LangiumDocument, node: AstNode): DocumentSymbol[] | undefined {
50+
if (this.isLeaf(node)) {
51+
return undefined;
52+
} else if (isSdsClass(node)) {
53+
if (node.body) {
54+
return super.getChildSymbols(document, node.body);
55+
} else {
56+
return undefined;
57+
}
58+
} else {
59+
return super.getChildSymbols(document, node);
60+
}
61+
}
62+
63+
private getDetails(node: AstNode): string | undefined {
64+
if (isSdsFunction(node) || isSdsSegment(node)) {
65+
const type = this.typeComputer.computeType(node);
66+
return type?.toString();
67+
}
68+
return undefined;
69+
}
70+
71+
private getTags(node: AstNode): SymbolTag[] | undefined {
72+
if (isSdsAnnotatedObject(node) && this.builtinAnnotations.isDeprecated(node)) {
73+
return [SymbolTag.Deprecated];
74+
} else {
75+
return undefined;
76+
}
77+
}
78+
79+
private isLeaf(node: AstNode): boolean {
80+
return (
81+
isSdsAnnotation(node) ||
82+
isSdsAttribute(node) ||
83+
isSdsEnumVariant(node) ||
84+
isSdsFunction(node) ||
85+
isSdsPipeline(node) ||
86+
isSdsSegment(node)
87+
);
88+
}
89+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { AstNode, AstNodeDescription, hasContainerOfType, isAstNode, NodeKindProvider } from 'langium';
2+
import { CompletionItemKind, SymbolKind } from 'vscode-languageserver';
3+
import {
4+
isSdsClass,
5+
isSdsFunction,
6+
SdsAnnotation,
7+
SdsAttribute,
8+
SdsBlockLambdaResult,
9+
SdsClass,
10+
SdsEnum,
11+
SdsEnumVariant,
12+
SdsFunction,
13+
SdsModule,
14+
SdsParameter,
15+
SdsPipeline,
16+
SdsPlaceholder,
17+
SdsResult,
18+
SdsSegment,
19+
SdsTypeParameter,
20+
} from '../generated/ast.js';
21+
22+
export class SafeDsNodeKindProvider implements NodeKindProvider {
23+
getSymbolKind(nodeOrDescription: AstNode | AstNodeDescription): SymbolKind {
24+
// The WorkspaceSymbolProvider only passes descriptions, where the node might be undefined
25+
const node = this.getNode(nodeOrDescription);
26+
if (isSdsFunction(node) && hasContainerOfType(node, isSdsClass)) {
27+
return SymbolKind.Method;
28+
}
29+
30+
const type = this.getNodeType(nodeOrDescription);
31+
switch (type) {
32+
case SdsAnnotation:
33+
return SymbolKind.Interface;
34+
case SdsAttribute:
35+
return SymbolKind.Property;
36+
/* c8 ignore next 2 */
37+
case SdsBlockLambdaResult:
38+
return SymbolKind.Variable;
39+
case SdsClass:
40+
return SymbolKind.Class;
41+
case SdsEnum:
42+
return SymbolKind.Enum;
43+
case SdsEnumVariant:
44+
return SymbolKind.EnumMember;
45+
case SdsFunction:
46+
return SymbolKind.Function;
47+
case SdsModule:
48+
return SymbolKind.Package;
49+
/* c8 ignore next 2 */
50+
case SdsParameter:
51+
return SymbolKind.Variable;
52+
case SdsPipeline:
53+
return SymbolKind.Function;
54+
/* c8 ignore next 2 */
55+
case SdsPlaceholder:
56+
return SymbolKind.Variable;
57+
/* c8 ignore next 2 */
58+
case SdsResult:
59+
return SymbolKind.Variable;
60+
case SdsSegment:
61+
return SymbolKind.Function;
62+
/* c8 ignore next 2 */
63+
case SdsTypeParameter:
64+
return SymbolKind.TypeParameter;
65+
/* c8 ignore next 2 */
66+
default:
67+
return SymbolKind.Null;
68+
}
69+
}
70+
71+
/* c8 ignore start */
72+
getCompletionItemKind(_nodeOrDescription: AstNode | AstNodeDescription) {
73+
return CompletionItemKind.Reference;
74+
}
75+
76+
/* c8 ignore stop */
77+
78+
private getNode(nodeOrDescription: AstNode | AstNodeDescription): AstNode | undefined {
79+
if (isAstNode(nodeOrDescription)) {
80+
return nodeOrDescription;
81+
} /* c8 ignore start */ else {
82+
return nodeOrDescription.node;
83+
} /* c8 ignore stop */
84+
}
85+
86+
private getNodeType(nodeOrDescription: AstNode | AstNodeDescription): string {
87+
if (isAstNode(nodeOrDescription)) {
88+
return nodeOrDescription.$type;
89+
} /* c8 ignore start */ else {
90+
return nodeOrDescription.type;
91+
} /* c8 ignore stop */
92+
}
93+
}

src/language/lsp/safe-ds-semantic-token-provider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import {
22
AbstractSemanticTokenProvider,
33
AllSemanticTokenTypes,
44
AstNode,
5+
DefaultSemanticTokenOptions,
56
hasContainerOfType,
67
SemanticTokenAcceptor,
7-
DefaultSemanticTokenOptions,
88
} from 'langium';
99
import {
1010
isSdsAnnotation,
@@ -29,7 +29,7 @@ import {
2929
isSdsTypeParameter,
3030
isSdsTypeParameterConstraint,
3131
} from '../generated/ast.js';
32-
import { SemanticTokenModifiers, SemanticTokenTypes } from 'vscode-languageserver-types';
32+
import { SemanticTokenModifiers, SemanticTokenTypes } from 'vscode-languageserver';
3333
import { SafeDsServices } from '../safe-ds-module.js';
3434
import { SafeDsClasses } from '../builtins/safe-ds-classes.js';
3535

src/language/partialEvaluation/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ class UnknownEvaluatedNodeClass extends EvaluatedNode {
371371
}
372372

373373
override toString(): string {
374-
return '$unknown';
374+
return '?';
375375
}
376376
}
377377

src/language/safe-ds-module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { SafeDsPartialEvaluator } from './partialEvaluation/safe-ds-partial-eval
2626
import { SafeDsSemanticTokenProvider } from './lsp/safe-ds-semantic-token-provider.js';
2727
import { SafeDsTypeChecker } from './typing/safe-ds-type-checker.js';
2828
import { SafeDsCoreTypes } from './typing/safe-ds-core-types.js';
29+
import { SafeDsNodeKindProvider } from './lsp/safe-ds-node-kind-provider.js';
30+
import { SafeDsDocumentSymbolProvider } from './lsp/safe-ds-document-symbol-provider.js';
2931

3032
/**
3133
* Declaration of custom services - add your own service classes here.
@@ -75,6 +77,7 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
7577
NodeMapper: (services) => new SafeDsNodeMapper(services),
7678
},
7779
lsp: {
80+
DocumentSymbolProvider: (services) => new SafeDsDocumentSymbolProvider(services),
7881
Formatter: () => new SafeDsFormatter(),
7982
SemanticTokenProvider: (services) => new SafeDsSemanticTokenProvider(services),
8083
},
@@ -99,6 +102,9 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
99102
export type SafeDsSharedServices = LangiumSharedServices;
100103

101104
export const SafeDsSharedModule: Module<SafeDsSharedServices, DeepPartial<SafeDsSharedServices>> = {
105+
lsp: {
106+
NodeKindProvider: () => new SafeDsNodeKindProvider(),
107+
},
102108
workspace: {
103109
WorkspaceManager: (services) => new SafeDsWorkspaceManager(services),
104110
},

src/language/typing/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ class UnknownTypeClass extends Type {
350350
}
351351

352352
toString(): string {
353-
return '$Unknown';
353+
return '?';
354354
}
355355
}
356356

src/language/validation/builtins/deprecated.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
import { SafeDsServices } from '../../safe-ds-module.js';
1414
import { isRequiredParameter } from '../../helpers/nodeProperties.js';
1515
import { parameterCanBeAnnotated } from '../other/declarations/annotationCalls.js';
16-
import { DiagnosticTag } from 'vscode-languageserver-types';
16+
import { DiagnosticTag } from 'vscode-languageserver';
1717

1818
export const CODE_DEPRECATED_ASSIGNED_RESULT = 'deprecated/assigned-result';
1919
export const CODE_DEPRECATED_CALLED_ANNOTATION = 'deprecated/called-annotation';

src/language/validation/other/declarations/placeholders.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { getContainerOfType, ValidationAcceptor } from 'langium';
1111
import { SafeDsServices } from '../../../safe-ds-module.js';
1212
import { statementsOrEmpty } from '../../../helpers/nodeProperties.js';
1313
import { last } from 'radash';
14-
import { DiagnosticTag } from 'vscode-languageserver-types';
14+
import { DiagnosticTag } from 'vscode-languageserver';
1515

1616
export const CODE_PLACEHOLDER_ALIAS = 'placeholder/alias';
1717
export const CODE_PLACEHOLDER_UNUSED = 'placeholder/unused';

0 commit comments

Comments
 (0)