Skip to content

Commit

Permalink
fix: renaming of declarations that are imported under an alias (#825)
Browse files Browse the repository at this point in the history
Closes #635

### Summary of Changes

Declarations that are imported under an alias are now correctly ignored
during renaming. The renaming behavior is always the same, no matter
whether it is triggered on a declaration or any reference. An alias can
only be changed manually.
  • Loading branch information
lars-reimann authored Jan 15, 2024
1 parent a74b8e0 commit 9f7363d
Show file tree
Hide file tree
Showing 5 changed files with 481 additions and 8 deletions.
15 changes: 13 additions & 2 deletions packages/safe-ds-lang/src/language/helpers/nodeProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
isSdsModuleMember,
isSdsParameter,
isSdsPlaceholder,
isSdsQualifiedImport,
isSdsSegment,
isSdsTypeParameterList,
SdsAbstractCall,
Expand Down Expand Up @@ -232,8 +233,18 @@ export const getImports = (node: SdsModule | undefined): SdsImport[] => {
return node?.imports ?? [];
};

export const getImportedDeclarations = (node: SdsQualifiedImport | undefined): SdsImportedDeclaration[] => {
return node?.importedDeclarationList?.importedDeclarations ?? [];
export const getImportedDeclarations = (node: SdsModule | SdsQualifiedImport | undefined): SdsImportedDeclaration[] => {
if (isSdsModule(node)) {
return getImports(node).flatMap((imp) => {
if (isSdsQualifiedImport(imp)) {
return getImportedDeclarations(imp);
} else {
return [];
}
});
} else {
return node?.importedDeclarationList?.importedDeclarations ?? [];
}
};

export const getLiterals = (node: SdsLiteralType | undefined): SdsLiteral[] => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { type AstNode, DefaultDocumentSymbolProvider, type LangiumDocument } from 'langium';
import type { DocumentSymbol } from 'vscode-languageserver';
import type { SafeDsAnnotations } from '../builtins/safe-ds-annotations.js';
import {
isSdsAnnotation,
isSdsAttribute,
Expand All @@ -11,20 +10,15 @@ import {
isSdsSegment,
} from '../generated/ast.js';
import type { SafeDsServices } from '../safe-ds-module.js';
import type { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
import type { SafeDsNodeInfoProvider } from './safe-ds-node-info-provider.js';

export class SafeDsDocumentSymbolProvider extends DefaultDocumentSymbolProvider {
private readonly builtinAnnotations: SafeDsAnnotations;
private readonly nodeInfoProvider: SafeDsNodeInfoProvider;
private readonly typeComputer: SafeDsTypeComputer;

constructor(services: SafeDsServices) {
super(services);

this.builtinAnnotations = services.builtins.Annotations;
this.nodeInfoProvider = services.lsp.NodeInfoProvider;
this.typeComputer = services.types.TypeComputer;
}

protected override getSymbol(document: LangiumDocument, node: AstNode): DocumentSymbol[] {
Expand Down
135 changes: 135 additions & 0 deletions packages/safe-ds-lang/src/language/lsp/safe-ds-rename-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {
AstNode,
AstNodeLocator,
DefaultRenameProvider,
findDeclarationNodeAtOffset,
getContainerOfType,
LangiumDocument,
LangiumDocuments,
ReferenceDescription,
Stream,
URI,
} from 'langium';
import { Position, RenameParams, TextEdit, WorkspaceEdit } from 'vscode-languageserver';
import { SafeDsServices } from '../safe-ds-module.js';
import { isSdsImportedDeclaration, isSdsModule } from '../generated/ast.js';
import { getImportedDeclarations } from '../helpers/nodeProperties.js';

export class SafeDsRenameProvider extends DefaultRenameProvider {
private readonly astNodeLocator: AstNodeLocator;
private readonly langiumDocuments: LangiumDocuments;

constructor(services: SafeDsServices) {
super(services);

this.astNodeLocator = services.workspace.AstNodeLocator;
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
}

override async rename(document: LangiumDocument, params: RenameParams): Promise<WorkspaceEdit | undefined> {
const references = this.findReferencesToRename(document, params.position);
return this.createWorkspaceEdit(references, params.newName);
}

private findReferencesToRename(
document: LangiumDocument,
position: Position,
): Stream<ReferenceDescription> | undefined {
const rootNode = document.parseResult.value.$cstNode;
if (!rootNode) {
/* c8 ignore next 2 */
return undefined;
}

const offset = document.textDocument.offsetAt(position);
const leafNode = findDeclarationNodeAtOffset(rootNode, offset, this.grammarConfig.nameRegexp);
if (!leafNode) {
/* c8 ignore next 2 */
return undefined;
}

const targetNode = this.references.findDeclaration(leafNode);
if (!targetNode) {
/* c8 ignore next 2 */
return undefined;
}

const options = { onlyLocal: false, includeDeclaration: true };
return this.references.findReferences(targetNode, options).filter((ref) => this.mustBeRenamed(ref));
}

private mustBeRenamed(ref: ReferenceDescription): boolean {
// References in the same file must always be renamed
if (ref.local) {
return true;
}

// References in imported declaration nodes must always be renamed
const sourceNode = this.getAstNode(ref.sourceUri, ref.sourcePath);
if (isSdsImportedDeclaration(sourceNode)) {
return true;
}

// Other references must be renamed unless they are imported under an alias
const sourceModule = getContainerOfType(sourceNode, isSdsModule);
if (!sourceModule) {
/* c8 ignore next 2 */
return false;
}

const referenceText = this.getReferenceText(ref);
if (!referenceText) {
/* c8 ignore next 2 */
return false;
}

const targetNode = this.getAstNode(ref.targetUri, ref.targetPath);
return !getImportedDeclarations(sourceModule).some((imp) => {
return imp.declaration?.ref === targetNode && imp.alias?.alias === referenceText;
});
}

private getAstNode(uri: URI, path: string): AstNode | undefined {
if (!this.langiumDocuments.hasDocument(uri)) {
/* c8 ignore next 2 */
return undefined;
}

const document = this.langiumDocuments.getOrCreateDocument(uri);
return this.astNodeLocator.getAstNode(document.parseResult.value, path);
}

private getReferenceText(ref: ReferenceDescription): string | undefined {
if (!this.langiumDocuments.hasDocument(ref.sourceUri)) {
/* c8 ignore next 2 */
return undefined;
}

const document = this.langiumDocuments.getOrCreateDocument(ref.sourceUri);
return document.textDocument.getText(ref.segment.range);
}

private createWorkspaceEdit(
references: Stream<ReferenceDescription> | undefined,
newName: string,
): WorkspaceEdit | undefined {
if (!references) {
/* c8 ignore next 2 */
return undefined;
}

const changes: Record<string, TextEdit[]> = {};

references.forEach((ref) => {
const change = TextEdit.replace(ref.segment.range, newName);
const uri = ref.sourceUri.toString();
if (!changes[uri]) {
changes[uri] = [];
}

changes[uri]!.push(change);
});

return { changes };
}
}
2 changes: 2 additions & 0 deletions packages/safe-ds-lang/src/language/safe-ds-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { SafeDsPackageManager } from './workspace/safe-ds-package-manager.js';
import { SafeDsWorkspaceManager } from './workspace/safe-ds-workspace-manager.js';
import { SafeDsPurityComputer } from './purity/safe-ds-purity-computer.js';
import { SafeDsSettingsProvider } from './workspace/safe-ds-settings-provider.js';
import { SafeDsRenameProvider } from './lsp/safe-ds-rename-provider.js';

/**
* Declaration of custom services - add your own service classes here.
Expand Down Expand Up @@ -124,6 +125,7 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
Formatter: () => new SafeDsFormatter(),
InlayHintProvider: (services) => new SafeDsInlayHintProvider(services),
NodeInfoProvider: (services) => new SafeDsNodeInfoProvider(services),
RenameProvider: (services) => new SafeDsRenameProvider(services),
SemanticTokenProvider: (services) => new SafeDsSemanticTokenProvider(services),
SignatureHelp: (services) => new SafeDsSignatureHelpProvider(services),
TypeHierarchyProvider: (services) => new SafeDsTypeHierarchyProvider(services),
Expand Down
Loading

0 comments on commit 9f7363d

Please sign in to comment.