Skip to content

Commit 4fd8d86

Browse files
feat: extensions for the NodeMapper (#606)
### Summary of Changes The `NodeMapper` has gained new capabilities. It can now map * results to yields, * parameters to references, * placeholders to references. These are needed for various validation checks. --------- Co-authored-by: megalinter-bot <[email protected]>
1 parent 25c8707 commit 4fd8d86

23 files changed

+735
-321
lines changed

src/language/builtins/safe-ds-core-classes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SafeDsServices } from '../safe-ds-module.js';
22
import { resolveRelativePathToBuiltinFile } from './fileFinder.js';
33
import { isSdsClass, isSdsModule, SdsClass } from '../generated/ast.js';
44
import { LangiumDocuments } from 'langium';
5-
import { moduleMembersOrEmpty } from '../helpers/shortcuts.js';
5+
import { moduleMembersOrEmpty } from '../helpers/nodeProperties.js';
66

77
const CORE_CLASSES_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreClasses.sdsstub');
88

@@ -23,9 +23,10 @@ export class SafeDsCoreClasses {
2323
return this.cachedAny;
2424
}
2525

26-
private cachedBoolean: SdsClass | undefined;
2726
/* c8 ignore stop */
2827

28+
private cachedBoolean: SdsClass | undefined;
29+
2930
get Boolean(): SdsClass | undefined {
3031
if (!this.cachedBoolean) {
3132
this.cachedBoolean = this.getClass('Boolean');

src/language/formatting/safe-ds-formatter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import {
99
isAstNode,
1010
} from 'langium';
1111
import * as ast from '../generated/ast.js';
12-
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/shortcuts.js';
1312
import noSpace = Formatting.noSpace;
1413
import newLine = Formatting.newLine;
1514
import newLines = Formatting.newLines;
1615
import oneSpace = Formatting.oneSpace;
1716
import indent = Formatting.indent;
17+
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/nodeProperties.js';
1818

1919
const newLinesWithIndent = function (count: number, options?: FormattingActionOptions): FormattingAction {
2020
return {
File renamed without changes.

src/language/helpers/checks.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Returns the unique element in the array, or `undefined` if none or multiple exist.
3+
*/
4+
export const uniqueOrUndefined = <T>(elements: T[]): T | undefined => {
5+
if (elements.length === 1) {
6+
return elements[0];
7+
}
8+
9+
return undefined;
10+
};
11+
12+
/**
13+
* Returns the elements of the array that are labeled the same as a previous element. The first element with a label is
14+
* not included. Neither are elements with an undefined label.
15+
*/
16+
export const duplicatesBy = function* <T, K>(
17+
elements: Iterable<T>,
18+
labeler: (element: T) => K | undefined,
19+
): Generator<T, void> {
20+
const knownLabels = new Set<K>();
21+
22+
for (const element of elements) {
23+
const label = labeler(element);
24+
if (label === undefined) {
25+
continue;
26+
}
27+
28+
if (knownLabels.has(label)) {
29+
yield element;
30+
} else {
31+
knownLabels.add(label);
32+
}
33+
}
34+
};

src/language/helpers/idManager.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Handles the mapping of objects, usually nodes of an Safe-DS AST, to their IDs.
3+
*/
4+
export class IdManager<T extends WeakKey> {
5+
/**
6+
* Maps an object to an ID.
7+
*/
8+
private objToId: WeakMap<T, Id> = new WeakMap();
9+
10+
/**
11+
* The next available ID.
12+
*/
13+
private nextId = 0;
14+
15+
/**
16+
* Assigns the next available ID to the given object unless it already has one and returns the ID for this object.
17+
*/
18+
assignId(obj: T): Id {
19+
if (!this.objToId.has(obj)) {
20+
this.objToId.set(obj, this.nextId++);
21+
}
22+
return this.objToId.get(obj)!;
23+
}
24+
25+
/**
26+
* Removes all mappings between object and ID and resets the counter.
27+
*/
28+
reset() {
29+
this.objToId = new WeakMap();
30+
this.nextId = 0;
31+
}
32+
}
33+
34+
export type Id = number;
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import {
2+
isSdsAssignment,
3+
isSdsAttribute,
4+
isSdsBlockLambdaResult,
5+
isSdsClass,
6+
isSdsDeclaration,
7+
isSdsEnum,
8+
isSdsEnumVariant,
9+
isSdsFunction,
10+
isSdsModule,
11+
isSdsModuleMember,
12+
isSdsPlaceholder,
13+
isSdsSegment,
14+
isSdsTypeParameterList,
15+
SdsAbstractCall,
16+
SdsAnnotatedObject,
17+
SdsAnnotationCall,
18+
SdsArgument,
19+
SdsAssignee,
20+
SdsAssignment,
21+
SdsBlock,
22+
SdsBlockLambda,
23+
SdsBlockLambdaResult,
24+
SdsCallable,
25+
SdsClass,
26+
SdsClassMember,
27+
SdsDeclaration,
28+
SdsEnum,
29+
SdsEnumVariant,
30+
SdsImport,
31+
SdsImportedDeclaration,
32+
SdsLiteral,
33+
SdsLiteralType,
34+
SdsModule,
35+
SdsModuleMember,
36+
SdsNamedTypeDeclaration,
37+
SdsParameter,
38+
SdsPlaceholder,
39+
SdsQualifiedImport,
40+
SdsResult,
41+
SdsResultList,
42+
SdsStatement,
43+
SdsTypeArgument,
44+
SdsTypeArgumentList,
45+
SdsTypeParameter,
46+
SdsTypeParameterList,
47+
} from '../generated/ast.js';
48+
import { AstNode, getContainerOfType, stream } from 'langium';
49+
50+
// -------------------------------------------------------------------------------------------------
51+
// Checks
52+
// -------------------------------------------------------------------------------------------------
53+
54+
export const isInternal = (node: SdsDeclaration): boolean => {
55+
return isSdsSegment(node) && node.visibility === 'internal';
56+
};
57+
58+
export const isNamedArgument = (node: SdsArgument): boolean => {
59+
return Boolean(node.parameter);
60+
};
61+
62+
export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => {
63+
return Boolean(node.typeParameter);
64+
};
65+
66+
export const isStatic = (node: SdsClassMember): boolean => {
67+
if (isSdsClass(node) || isSdsEnum(node)) {
68+
return true;
69+
} else if (isSdsAttribute(node)) {
70+
return node.isStatic;
71+
} else if (isSdsFunction(node)) {
72+
return node.isStatic;
73+
} else {
74+
/* c8 ignore next 2 */
75+
return false;
76+
}
77+
};
78+
79+
// -------------------------------------------------------------------------------------------------
80+
// Accessors for list elements
81+
// -------------------------------------------------------------------------------------------------
82+
83+
export const annotationCallsOrEmpty = (node: SdsAnnotatedObject | undefined): SdsAnnotationCall[] => {
84+
if (!node) {
85+
/* c8 ignore next 2 */
86+
return [];
87+
}
88+
89+
if (isSdsDeclaration(node)) {
90+
return node?.annotationCallList?.annotationCalls ?? node?.annotationCalls ?? [];
91+
} else {
92+
/* c8 ignore next 2 */
93+
return node?.annotationCalls ?? [];
94+
}
95+
};
96+
export const argumentsOrEmpty = (node: SdsAbstractCall | undefined): SdsArgument[] => {
97+
return node?.argumentList?.arguments ?? [];
98+
};
99+
export const assigneesOrEmpty = (node: SdsAssignment | undefined): SdsAssignee[] => {
100+
return node?.assigneeList?.assignees ?? [];
101+
};
102+
export const blockLambdaResultsOrEmpty = (node: SdsBlockLambda | undefined): SdsBlockLambdaResult[] => {
103+
return stream(statementsOrEmpty(node?.body))
104+
.filter(isSdsAssignment)
105+
.flatMap(assigneesOrEmpty)
106+
.filter(isSdsBlockLambdaResult)
107+
.toArray();
108+
};
109+
export const importedDeclarationsOrEmpty = (node: SdsQualifiedImport | undefined): SdsImportedDeclaration[] => {
110+
return node?.importedDeclarationList?.importedDeclarations ?? [];
111+
};
112+
113+
export const literalsOrEmpty = (node: SdsLiteralType | undefined): SdsLiteral[] => {
114+
return node?.literalList?.literals ?? [];
115+
};
116+
export const classMembersOrEmpty = (
117+
node: SdsClass | undefined,
118+
filterFunction: (member: SdsClassMember) => boolean = () => true,
119+
): SdsClassMember[] => {
120+
return node?.body?.members?.filter(filterFunction) ?? [];
121+
};
122+
123+
export const enumVariantsOrEmpty = (node: SdsEnum | undefined): SdsEnumVariant[] => {
124+
return node?.body?.variants ?? [];
125+
};
126+
127+
export const importsOrEmpty = (node: SdsModule | undefined): SdsImport[] => {
128+
return node?.imports ?? [];
129+
};
130+
131+
export const moduleMembersOrEmpty = (node: SdsModule | undefined): SdsModuleMember[] => {
132+
return node?.members?.filter(isSdsModuleMember) ?? [];
133+
};
134+
135+
export const packageNameOrUndefined = (node: AstNode | undefined): string | undefined => {
136+
return getContainerOfType(node, isSdsModule)?.name;
137+
};
138+
139+
export const parametersOrEmpty = (node: SdsCallable | undefined): SdsParameter[] => {
140+
return node?.parameterList?.parameters ?? [];
141+
};
142+
143+
export const placeholdersOrEmpty = (node: SdsBlock | undefined): SdsPlaceholder[] => {
144+
return stream(statementsOrEmpty(node))
145+
.filter(isSdsAssignment)
146+
.flatMap(assigneesOrEmpty)
147+
.filter(isSdsPlaceholder)
148+
.toArray();
149+
};
150+
151+
export const resultsOrEmpty = (node: SdsResultList | undefined): SdsResult[] => {
152+
return node?.results ?? [];
153+
};
154+
155+
export const statementsOrEmpty = (node: SdsBlock | undefined): SdsStatement[] => {
156+
return node?.statements ?? [];
157+
};
158+
159+
export const typeArgumentsOrEmpty = (node: SdsTypeArgumentList | undefined): SdsTypeArgument[] => {
160+
return node?.typeArguments ?? [];
161+
};
162+
163+
export const typeParametersOrEmpty = (
164+
node: SdsTypeParameterList | SdsNamedTypeDeclaration | undefined,
165+
): SdsTypeParameter[] => {
166+
if (!node) {
167+
return [];
168+
}
169+
170+
if (isSdsTypeParameterList(node)) {
171+
return node.typeParameters;
172+
} else if (isSdsClass(node)) {
173+
return typeParametersOrEmpty(node.typeParameterList);
174+
} else if (isSdsEnumVariant(node)) {
175+
return typeParametersOrEmpty(node.typeParameterList);
176+
} /* c8 ignore start */ else {
177+
return [];
178+
} /* c8 ignore stop */
179+
};

0 commit comments

Comments
 (0)