Skip to content

Commit

Permalink
feat: extensions for the NodeMapper (#606)
Browse files Browse the repository at this point in the history
### 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 <129584137+megalinter-bot@users.noreply.github.com>
  • Loading branch information
lars-reimann and megalinter-bot authored Oct 6, 2023
1 parent 25c8707 commit 4fd8d86
Show file tree
Hide file tree
Showing 23 changed files with 735 additions and 321 deletions.
5 changes: 3 additions & 2 deletions src/language/builtins/safe-ds-core-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SafeDsServices } from '../safe-ds-module.js';
import { resolveRelativePathToBuiltinFile } from './fileFinder.js';
import { isSdsClass, isSdsModule, SdsClass } from '../generated/ast.js';
import { LangiumDocuments } from 'langium';
import { moduleMembersOrEmpty } from '../helpers/shortcuts.js';
import { moduleMembersOrEmpty } from '../helpers/nodeProperties.js';

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

Expand All @@ -23,9 +23,10 @@ export class SafeDsCoreClasses {
return this.cachedAny;
}

private cachedBoolean: SdsClass | undefined;
/* c8 ignore stop */

private cachedBoolean: SdsClass | undefined;

get Boolean(): SdsClass | undefined {
if (!this.cachedBoolean) {
this.cachedBoolean = this.getClass('Boolean');
Expand Down
2 changes: 1 addition & 1 deletion src/language/formatting/safe-ds-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {
isAstNode,
} from 'langium';
import * as ast from '../generated/ast.js';
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/shortcuts.js';
import noSpace = Formatting.noSpace;
import newLine = Formatting.newLine;
import newLines = Formatting.newLines;
import oneSpace = Formatting.oneSpace;
import indent = Formatting.indent;
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/nodeProperties.js';

const newLinesWithIndent = function (count: number, options?: FormattingActionOptions): FormattingAction {
return {
Expand Down
File renamed without changes.
36 changes: 0 additions & 36 deletions src/language/helpers/checks.ts

This file was deleted.

34 changes: 34 additions & 0 deletions src/language/helpers/collectionUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Returns the unique element in the array, or `undefined` if none or multiple exist.
*/
export const uniqueOrUndefined = <T>(elements: T[]): T | undefined => {
if (elements.length === 1) {
return elements[0];
}

return undefined;
};

/**
* Returns the elements of the array that are labeled the same as a previous element. The first element with a label is
* not included. Neither are elements with an undefined label.
*/
export const duplicatesBy = function* <T, K>(
elements: Iterable<T>,
labeler: (element: T) => K | undefined,
): Generator<T, void> {
const knownLabels = new Set<K>();

for (const element of elements) {
const label = labeler(element);
if (label === undefined) {
continue;
}

if (knownLabels.has(label)) {
yield element;
} else {
knownLabels.add(label);
}
}
};
34 changes: 34 additions & 0 deletions src/language/helpers/idManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Handles the mapping of objects, usually nodes of an Safe-DS AST, to their IDs.
*/
export class IdManager<T extends WeakKey> {
/**
* Maps an object to an ID.
*/
private objToId: WeakMap<T, Id> = new WeakMap();

/**
* The next available ID.
*/
private nextId = 0;

/**
* Assigns the next available ID to the given object unless it already has one and returns the ID for this object.
*/
assignId(obj: T): Id {
if (!this.objToId.has(obj)) {
this.objToId.set(obj, this.nextId++);
}
return this.objToId.get(obj)!;
}

/**
* Removes all mappings between object and ID and resets the counter.
*/
reset() {
this.objToId = new WeakMap();
this.nextId = 0;
}
}

export type Id = number;
179 changes: 179 additions & 0 deletions src/language/helpers/nodeProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import {
isSdsAssignment,
isSdsAttribute,
isSdsBlockLambdaResult,
isSdsClass,
isSdsDeclaration,
isSdsEnum,
isSdsEnumVariant,
isSdsFunction,
isSdsModule,
isSdsModuleMember,
isSdsPlaceholder,
isSdsSegment,
isSdsTypeParameterList,
SdsAbstractCall,
SdsAnnotatedObject,
SdsAnnotationCall,
SdsArgument,
SdsAssignee,
SdsAssignment,
SdsBlock,
SdsBlockLambda,
SdsBlockLambdaResult,
SdsCallable,
SdsClass,
SdsClassMember,
SdsDeclaration,
SdsEnum,
SdsEnumVariant,
SdsImport,
SdsImportedDeclaration,
SdsLiteral,
SdsLiteralType,
SdsModule,
SdsModuleMember,
SdsNamedTypeDeclaration,
SdsParameter,
SdsPlaceholder,
SdsQualifiedImport,
SdsResult,
SdsResultList,
SdsStatement,
SdsTypeArgument,
SdsTypeArgumentList,
SdsTypeParameter,
SdsTypeParameterList,
} from '../generated/ast.js';
import { AstNode, getContainerOfType, stream } from 'langium';

// -------------------------------------------------------------------------------------------------
// Checks
// -------------------------------------------------------------------------------------------------

export const isInternal = (node: SdsDeclaration): boolean => {
return isSdsSegment(node) && node.visibility === 'internal';
};

export const isNamedArgument = (node: SdsArgument): boolean => {
return Boolean(node.parameter);
};

export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => {
return Boolean(node.typeParameter);
};

export const isStatic = (node: SdsClassMember): boolean => {
if (isSdsClass(node) || isSdsEnum(node)) {
return true;
} else if (isSdsAttribute(node)) {
return node.isStatic;
} else if (isSdsFunction(node)) {
return node.isStatic;
} else {
/* c8 ignore next 2 */
return false;
}
};

// -------------------------------------------------------------------------------------------------
// Accessors for list elements
// -------------------------------------------------------------------------------------------------

export const annotationCallsOrEmpty = (node: SdsAnnotatedObject | undefined): SdsAnnotationCall[] => {
if (!node) {
/* c8 ignore next 2 */
return [];
}

if (isSdsDeclaration(node)) {
return node?.annotationCallList?.annotationCalls ?? node?.annotationCalls ?? [];
} else {
/* c8 ignore next 2 */
return node?.annotationCalls ?? [];
}
};
export const argumentsOrEmpty = (node: SdsAbstractCall | undefined): SdsArgument[] => {
return node?.argumentList?.arguments ?? [];
};
export const assigneesOrEmpty = (node: SdsAssignment | undefined): SdsAssignee[] => {
return node?.assigneeList?.assignees ?? [];
};
export const blockLambdaResultsOrEmpty = (node: SdsBlockLambda | undefined): SdsBlockLambdaResult[] => {
return stream(statementsOrEmpty(node?.body))
.filter(isSdsAssignment)
.flatMap(assigneesOrEmpty)
.filter(isSdsBlockLambdaResult)
.toArray();
};
export const importedDeclarationsOrEmpty = (node: SdsQualifiedImport | undefined): SdsImportedDeclaration[] => {
return node?.importedDeclarationList?.importedDeclarations ?? [];
};

export const literalsOrEmpty = (node: SdsLiteralType | undefined): SdsLiteral[] => {
return node?.literalList?.literals ?? [];
};
export const classMembersOrEmpty = (
node: SdsClass | undefined,
filterFunction: (member: SdsClassMember) => boolean = () => true,
): SdsClassMember[] => {
return node?.body?.members?.filter(filterFunction) ?? [];
};

export const enumVariantsOrEmpty = (node: SdsEnum | undefined): SdsEnumVariant[] => {
return node?.body?.variants ?? [];
};

export const importsOrEmpty = (node: SdsModule | undefined): SdsImport[] => {
return node?.imports ?? [];
};

export const moduleMembersOrEmpty = (node: SdsModule | undefined): SdsModuleMember[] => {
return node?.members?.filter(isSdsModuleMember) ?? [];
};

export const packageNameOrUndefined = (node: AstNode | undefined): string | undefined => {
return getContainerOfType(node, isSdsModule)?.name;
};

export const parametersOrEmpty = (node: SdsCallable | undefined): SdsParameter[] => {
return node?.parameterList?.parameters ?? [];
};

export const placeholdersOrEmpty = (node: SdsBlock | undefined): SdsPlaceholder[] => {
return stream(statementsOrEmpty(node))
.filter(isSdsAssignment)
.flatMap(assigneesOrEmpty)
.filter(isSdsPlaceholder)
.toArray();
};

export const resultsOrEmpty = (node: SdsResultList | undefined): SdsResult[] => {
return node?.results ?? [];
};

export const statementsOrEmpty = (node: SdsBlock | undefined): SdsStatement[] => {
return node?.statements ?? [];
};

export const typeArgumentsOrEmpty = (node: SdsTypeArgumentList | undefined): SdsTypeArgument[] => {
return node?.typeArguments ?? [];
};

export const typeParametersOrEmpty = (
node: SdsTypeParameterList | SdsNamedTypeDeclaration | undefined,
): SdsTypeParameter[] => {
if (!node) {
return [];
}

if (isSdsTypeParameterList(node)) {
return node.typeParameters;
} else if (isSdsClass(node)) {
return typeParametersOrEmpty(node.typeParameterList);
} else if (isSdsEnumVariant(node)) {
return typeParametersOrEmpty(node.typeParameterList);
} /* c8 ignore start */ else {
return [];
} /* c8 ignore stop */
};
Loading

0 comments on commit 4fd8d86

Please sign in to comment.