From dd90bfcd5224517b8ece1fd4dcc1b7a573bceddd Mon Sep 17 00:00:00 2001 From: PaddiM8 Date: Mon, 10 Jun 2024 20:05:19 +0200 Subject: [PATCH 1/3] Implement goto declaration Co-authored-by: Evan Hedbor --- server/src/analysis/reference.ts | 129 ++++++ server/src/analysis/resolveReference.ts | 561 ++++++++++++++++++++++++ server/src/analyzer.ts | 189 +++++++- server/src/server.ts | 74 +++- server/src/util/tree-sitter.ts | 232 ++++++++++ 5 files changed, 1182 insertions(+), 3 deletions(-) create mode 100644 server/src/analysis/reference.ts create mode 100644 server/src/analysis/resolveReference.ts diff --git a/server/src/analysis/reference.ts b/server/src/analysis/reference.ts new file mode 100644 index 0000000..5f9f598 --- /dev/null +++ b/server/src/analysis/reference.ts @@ -0,0 +1,129 @@ +import { ModelicaDocument } from '../project/document'; +import Parser from 'web-tree-sitter'; + +export type ReferenceKind = 'class' | 'variable'; + +export abstract class BaseUnresolvedReference { + /** + * The path to the symbol reference. + */ + public readonly symbols: string[]; + + public readonly kind: ReferenceKind | undefined; + + public constructor(symbols: string[], kind?: ReferenceKind) { + if (symbols.length === 0) { + throw new Error('Symbols length must be greater tham 0'); + } + + this.symbols = symbols; + this.kind = kind; + } + + public abstract isAbsolute(): this is UnresolvedAbsoluteReference; +} + +export class UnresolvedRelativeReference extends BaseUnresolvedReference { + /** + * The document that contains the `node`. + */ + public readonly document: ModelicaDocument; + + /** + * A `SyntaxNode` in which the symbol is in scope. + */ + public readonly node: Parser.SyntaxNode; + + public constructor( + document: ModelicaDocument, + node: Parser.SyntaxNode, + symbols: string[], + kind?: ReferenceKind, + ) { + super(symbols, kind); + this.document = document; + this.node = node; + } + + public isAbsolute(): this is UnresolvedAbsoluteReference { + return false; + } + + public toString(): string { + const start = this.node.startPosition; + return ( + `UnresolvedReference { ` + + `symbols: ${this.symbols.join('.')}, ` + + `kind: ${this.kind}, ` + + `position: ${start.row + 1}:${start.column + 1}, ` + + `document: "${this.document.path}" ` + + `}` + ); + } +} + +export class UnresolvedAbsoluteReference extends BaseUnresolvedReference { + public constructor(symbols: string[], kind?: ReferenceKind) { + super(symbols, kind); + } + + public isAbsolute(): this is UnresolvedAbsoluteReference { + return true; + } + + public toString(): string { + return ( + `UnresolvedReference { ` + + `symbols: .${this.symbols.join('.')}, ` + + `kind: ${this.kind} ` + + `}` + ); + } +} + +/** + * A possibly-valid reference to a symbol that must be resolved before use. + */ +export type UnresolvedReference = UnresolvedRelativeReference | UnresolvedAbsoluteReference; + +/** + * A valid, absolute reference to a symbol. + */ +export class ResolvedReference { + /** + * The document that contains the `node`. + */ + readonly document: ModelicaDocument; + + /** + * The node that declares/defines this symbol. + */ + readonly node: Parser.SyntaxNode; + + /** + * The full, absolute path to the symbol. + */ + readonly symbols: string[]; + + readonly kind: ReferenceKind; + + public constructor( + document: ModelicaDocument, + node: Parser.SyntaxNode, + symbols: string[], + kind: ReferenceKind, + ) { + if (symbols.length === 0) { + throw new Error('Symbols length must be greater than 0.'); + } + + this.document = document; + this.node = node; + this.symbols = symbols; + this.kind = kind; + } + + public toString(): string { + return `Reference { symbols: .${this.symbols.join('.')}, kind: ${this.kind} }`; + } +} diff --git a/server/src/analysis/resolveReference.ts b/server/src/analysis/resolveReference.ts new file mode 100644 index 0000000..0948f4b --- /dev/null +++ b/server/src/analysis/resolveReference.ts @@ -0,0 +1,561 @@ +import Parser from 'web-tree-sitter'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +import * as TreeSitterUtil from '../util/tree-sitter'; +import { + ReferenceKind, + ResolvedReference, + UnresolvedAbsoluteReference, + UnresolvedReference, + UnresolvedRelativeReference, +} from './reference'; +import { logger } from '../util/logger'; +import { ModelicaProject, ModelicaLibrary, ModelicaDocument } from '../project'; + +export type Resolution = 'declaration' | 'definition'; + +/** + * Locates the declaration or definition of a symbol reference. + * + * @param project the project + * @param reference a reference + * @param resolution the kind of symbol to search for + */ +export default function resolveReference( + project: ModelicaProject, + reference: UnresolvedReference, + resolution: Resolution, +): ResolvedReference | null { + logger.debug(`Resolving ${resolution} ${reference}`); + + if (resolution === 'definition') { + throw new Error('Resolving definitions not yet supported!'); + } + + if (reference instanceof UnresolvedAbsoluteReference) { + return resolveAbsoluteReference(project, reference); + } + + for (const ref of getAbsoluteReferenceCandidates(reference)) { + const resolved = resolveAbsoluteReference(project, ref); + if (resolved) { + return resolved; + } + } + + return null; +} + +/** + * Converts a relative reference to an absolute reference. + * + * @param reference a relative reference to a symbol declaration/definition + * @returns an absolute reference to that symbol, or `null` if no such symbol exists. + */ +function* getAbsoluteReferenceCandidates( + reference: UnresolvedRelativeReference, +): Generator { + logger.debug(`Checking candidates for ${reference}`); + + for (const local of findReferenceInDocument(reference)) { + if (local instanceof UnresolvedAbsoluteReference) { + logger.debug(`Found ${local}`); + yield local; + continue; + } + + const relativeReference = local ?? reference; + + const ancestors: string[] = []; + let currentNode: Parser.SyntaxNode | null = relativeReference.node; + while (currentNode) { + if (currentNode.type === 'class_definition') { + const identifier = TreeSitterUtil.getDeclaredIdentifiers(currentNode).at(0); + if (identifier) { + ancestors.unshift(identifier); + } + } + + currentNode = currentNode.parent; + } + + if (relativeReference.node.type === 'class_definition') { + ancestors.pop(); + } + + logger.debug(`Found ${relativeReference} with ancestors: [${ancestors}]`); + + const classPath = [...relativeReference.document.within, ...ancestors]; + while (true) { + yield new UnresolvedAbsoluteReference( + [...classPath, ...relativeReference.symbols], + relativeReference.kind, + ); + if (classPath.length === 0) { + break; + } + classPath.pop(); + } + } + + logger.debug(`Didn't find ${reference}`); +} + +/** + * Attempts to locate the definition of an `UnresolvedRelativeReference` within + * the referenced document. + * + * If the reference is present in the document, an `UnresolvedRelativeReference` + * pointing to the definition will be returned. If the reference may refer to an + * import, an `UnresolvedAbsoluteReference` will be returned. If the reference + * was not present at all, `undefined` will be returned. + * + * @param reference a reference to a local in which the `document` and `node` + * properties reference the usage of the symbol. + * @returns the reference candidates + */ +function* findReferenceInDocument( + reference: UnresolvedRelativeReference, +): Generator { + const maybeClass = reference.kind === 'class' || reference.kind === undefined; + const maybeVariable = reference.kind === 'variable' || reference.kind === undefined; + + if (maybeClass) { + if ( + TreeSitterUtil.isDefinition(reference.node) && + TreeSitterUtil.hasIdentifier(reference.node, reference.symbols[0]) + ) { + yield new UnresolvedRelativeReference( + reference.document, + reference.node, + reference.symbols, + 'class', + ); + return; + } + + const classDecl = reference.node.children.find( + (child) => + TreeSitterUtil.isDefinition(child) && + TreeSitterUtil.hasIdentifier(child, reference.symbols[0]), + ); + if (classDecl) { + logger.debug('Found local class'); + yield new UnresolvedRelativeReference( + reference.document, + classDecl, + reference.symbols, + 'class', + ); + return; + } + } + + if (maybeVariable) { + const varDecl = reference.node.children.find( + (child) => + TreeSitterUtil.isVariableDeclaration(child) && + TreeSitterUtil.hasIdentifier(child, reference.symbols[0]), + ); + if (varDecl) { + logger.debug('Found local variable'); + yield new UnresolvedRelativeReference( + reference.document, + varDecl, + reference.symbols, + 'variable', + ); + return; + } + } + + const declInClass = findDeclarationInClass( + reference.document, + reference.node, + reference.symbols, + reference.kind, + ); + if (declInClass) { + yield declInClass; + return; + } + + const importClauses = reference.node.parent?.children.filter( + (child) => child.type === 'import_clause', + ); + if (importClauses && importClauses.length > 0) { + for (const importClause of importClauses) { + const { importCandidate, wildcard } = resolveImportClause(reference.symbols, importClause); + if (importCandidate) { + if (wildcard) { + yield importCandidate; + } else { + yield importCandidate; + return; + } + } + } + } + + if (reference.node.parent) { + yield* findReferenceInDocument( + new UnresolvedRelativeReference( + reference.document, + reference.node.parent, + reference.symbols, + reference.kind, + ), + ); + return; + } + + logger.debug(`Not found in document. May be a global? ${reference}`); + yield undefined; + return; +} + +/** + * Searches for a declaration within a class (or a superclass). + * + * @param document the class' document + * @param classNode the `class_definition` syntax node referencing the class + * @param symbols the symbol to search for + * @param referenceKind the type of reference + * @returns an unresolved reference to the symbol, or `undefined` if not present + */ +function findDeclarationInClass( + document: ModelicaDocument, + classNode: Parser.SyntaxNode, + symbols: string[], + referenceKind: ReferenceKind | undefined, +): (UnresolvedRelativeReference & { kind: ReferenceKind }) | undefined { + if (classNode.type !== 'class_definition') { + return undefined; + } + + const elements = classNode + .childForFieldName('classSpecifier') + ?.children?.filter(TreeSitterUtil.isElementList) + ?.flatMap((element_list) => element_list.namedChildren) + ?.map((element) => [element, TreeSitterUtil.getDeclaredIdentifiers(element)] as const); + + if (!elements) { + logger.debug("Didn't find declaration in class"); + return undefined; + } + + const namedElement = elements.find( + ([element, idents]) => element.type === 'named_element' && idents.includes(symbols[0]), + ); + if (namedElement) { + logger.debug(`Resolved ${symbols[0]} to field: ${namedElement[1]}`); + + const classDef = namedElement[0].childForFieldName('classDefinition'); + if (classDef) { + return new UnresolvedRelativeReference( + document, + classDef, + symbols, + 'class', + ) as UnresolvedRelativeReference & { kind: 'class' }; + } + + const componentDef = namedElement[0].childForFieldName('componentClause')!; + + // TODO: this handles named_elements but what if it's an import clause? + return new UnresolvedRelativeReference( + document, + componentDef, + symbols, + 'variable', + ) as UnresolvedRelativeReference & { kind: 'variable' }; + } + + // only check superclasses if we know we're not looking for a class + if (referenceKind !== 'class') { + const extendsClauses = elements + .map(([element, _idents]) => element) + .filter((element) => element.type === 'extends_clause'); + for (const extendsClause of extendsClauses) { + const superclassType = TreeSitterUtil.getTypeSpecifier(extendsClause); + const unresolvedSuperclass = superclassType.isGlobal + ? new UnresolvedAbsoluteReference(superclassType.symbols, 'class') + : new UnresolvedRelativeReference(document, extendsClause, superclassType.symbols, 'class'); + + logger.debug( + `Resolving superclass ${unresolvedSuperclass} (of ${ + TreeSitterUtil.getDeclaredIdentifiers(classNode)[0] + })`, + ); + + // TODO: support "definition" resolution + const superclass = resolveReference(document.project, unresolvedSuperclass, 'declaration'); + if (!superclass) { + logger.warn(`Could not find superclass ${unresolvedSuperclass}`); + continue; + } + + logger.debug(`Checking superclass ${superclass}`); + const decl = findDeclarationInClass( + superclass.document, + superclass.node, + symbols, + 'variable', + ); + if (decl) { + logger.debug(`Declaration ${decl} found in superclass ${superclass}`); + return decl; + } + } + } + + return undefined; +} + +interface ResolveImportClauseResult { + /** + * The resolved import candidate, or `undefined` if none was found. + */ + importCandidate?: UnresolvedAbsoluteReference; + /** + * `true` if this was a wildcard import, and we are not sure if this import + * even exists. `false` if this was not a wildcard import. + */ + wildcard: boolean; +} + +/** + * Given an import clause and a potentially-imported symbol, returns an + * unresolved reference to check. + * + * @param symbols a symbol that may have been imported + * @param importClause an import clause + * @returns the resolved import + */ +function resolveImportClause( + symbols: string[], + importClause: Parser.SyntaxNode, +): ResolveImportClauseResult { + // imports are always relative according to the grammar + const importPath = TreeSitterUtil.getTypeSpecifier( + importClause.childForFieldName('name')!, + ).symbols; + + // wildcard import: import a.b.*; + const isWildcard = importClause.childForFieldName('wildcard') != null; + if (isWildcard) { + const importCandidate = new UnresolvedAbsoluteReference([...importPath, ...symbols]); + logger.debug(`Candidate: ${importCandidate} (from import ${importPath.join('.')}.*)`); + + // TODO: this should probably not resolve the reference fully, then immediately + // discard it so it can do so again. + return { importCandidate, wildcard: true }; + } + + // import alias: import z = a.b.c; + // TODO: Determine if import aliases should be counted as "declarations". + // If so, then we should stop here for decls when symbols.length == 1. + const alias = importClause.childForFieldName('alias')?.text; + if (alias && alias === symbols[0]) { + const importCandidate = new UnresolvedAbsoluteReference([...importPath, ...symbols.slice(1)]); + logger.debug(`Candidate: ${importCandidate} (from import ${alias} = ${importPath.join('.')})`); + + return { importCandidate, wildcard: false }; + } + + // multi-import: import a.b.{c, d, e}; + const childImports = importClause + .childForFieldName('imports') + ?.namedChildren?.filter((node) => node.type === 'IDENT') + ?.map((node) => node.text); + + if (childImports?.some((name) => name === symbols[0])) { + const importCandidate = new UnresolvedAbsoluteReference([...importPath, ...symbols]); + const importString = `import ${importPath.join('.')}.{ ${childImports.join(', ')} }`; + logger.debug(`Candidate: ${importCandidate} (from ${importString})`); + + return { importCandidate, wildcard: false }; + } + + // normal import: import a.b.c; + if (importPath.at(-1) === symbols[0]) { + const importCandidate = new UnresolvedAbsoluteReference([...importPath, ...symbols.slice(1)]); + logger.debug(`Candidate: ${importCandidate} (from import ${importPath.join('.')})`); + + return { importCandidate, wildcard: false }; + } + + return { wildcard: false }; +} + +/** + * Locates the declaration/definition of an absolute symbol reference. + * + * @param reference an absolute reference + * @returns a resolved reference, or `null` if no such symbol exists + */ +function resolveAbsoluteReference( + project: ModelicaProject, + reference: UnresolvedAbsoluteReference, +): ResolvedReference | null { + if (!(reference instanceof UnresolvedAbsoluteReference)) { + throw new Error(`Reference is not an UnresolvedAbsoluteReference: ${reference}`); + } + + logger.debug(`Resolving ${reference}`); + + const library = project.libraries.find((lib) => lib.name === reference.symbols[0]); + if (library == null) { + logger.debug(`Couldn't find library: ${reference.symbols[0]}`); + return null; + } + + let alreadyResolved: ResolvedReference | null = null; + for (let i = 0; i < reference.symbols.length; i++) { + alreadyResolved = resolveNext(library, reference.symbols[i], alreadyResolved); + if (alreadyResolved == null) { + return null; + } + + // If we're not done with the reference chain, we need to make sure that we + // know the type of the variable in order to check its child variables + if ( + i < reference.symbols.length - 1 && + TreeSitterUtil.isVariableDeclaration(alreadyResolved.node) + ) { + const classRef = variableRefToClassRef(alreadyResolved); + if (classRef == null) { + logger.debug(`Failed to find type of var ${alreadyResolved}`); + return null; + } + + alreadyResolved = classRef; + } + } + + logger.debug(`Resolved symbol ${alreadyResolved}`); + + return alreadyResolved; +} + +/** + * Performs a single iteration of the resolution algorithm. + * + * @param nextSymbol the next symbol to resolve + * @param parentReference a resolved reference (to a class) + * @returns the next resolved reference + */ +function resolveNext( + library: ModelicaLibrary, + nextSymbol: string, + parentReference: ResolvedReference | null, +): ResolvedReference | null { + // If at the root level, find the root package + if (!parentReference) { + const documentPath = path.join(library.path, 'package.mo'); + const [document, packageClass] = getPackageClassFromFilePath(library, documentPath, nextSymbol); + if (!document || !packageClass) { + logger.debug(`Couldn't find package class: ${nextSymbol} in ${documentPath}`); + return null; + } + + return new ResolvedReference(document, packageClass, [nextSymbol], 'class'); + } + + const dirName = path.dirname(parentReference.document.path); + const potentialPaths = [ + path.join(dirName, `${nextSymbol}.mo`), + path.join(dirName, `${nextSymbol}/package.mo`), + ]; + + for (const documentPath of potentialPaths) { + if (!fs.existsSync(documentPath)) { + continue; + } + + const [document, packageClass] = getPackageClassFromFilePath(library, documentPath, nextSymbol); + if (!document || !packageClass) { + logger.debug(`Couldn't find package class: ${nextSymbol} in ${documentPath}`); + return null; + } + + return new ResolvedReference( + document, + packageClass, + [...parentReference.symbols, nextSymbol], + 'class', + ); + } + + // TODO: The `kind` parameter here should be `undefined` unless + // `resolveReference` was called with kind = "class" by the superclass + // handling section in findDeclarationInClass. ...or something like that + // As it is now, we don't know if `child` is a class or variable. We can't use + // `undefined` to indicate this because this results in infinite recursion. + // This issue causes us to be unable to look up variables declared in a + // superclass of a member variable. A redesign might be necessary to resolve + // this. Perhaps if we could keep track of which classes we already visited, + // we wouldn't need the whole "class"/"variable" trick at all! + const child = findDeclarationInClass( + parentReference.document, + parentReference.node, + [nextSymbol], + parentReference.kind, + ); + if (child) { + return new ResolvedReference( + child.document, + child.node, + [...parentReference.symbols, nextSymbol], + child.kind, + ); + } + + logger.debug(`Couldn't find: .${parentReference.symbols.join('.')}.${nextSymbol}`); + + return null; +} + +function getPackageClassFromFilePath( + library: ModelicaLibrary, + filePath: string, + symbol: string, +): [ModelicaDocument | undefined, Parser.SyntaxNode | undefined] { + const document = library.documents.get(filePath); + if (!document) { + logger.debug(`getPackageClassFromFilePath: Couldn't find document ${filePath}`); + return [undefined, undefined]; + } + + const node = TreeSitterUtil.findFirst( + document.tree.rootNode, + (child) => child.type === 'class_definition' && TreeSitterUtil.hasIdentifier(child, symbol), + ); + if (!node) { + logger.debug( + `getPackageClassFromFilePath: Couldn't find package class node ${symbol} in ${filePath}`, + ); + return [document, undefined]; + } + + return [document, node]; +} + +/** + * Finds the type of a variable declaration and returns a reference to that + * type. + * + * @param varRef a reference to a variable declaration/definition + * @returns a reference to the class definition, or `null` if the type is not a + * class (e.g. a builtin like `Real`) + */ +function variableRefToClassRef(varRef: ResolvedReference): ResolvedReference | null { + const type = TreeSitterUtil.getTypeSpecifier(varRef.node); + + const typeRef = type.isGlobal + ? new UnresolvedAbsoluteReference(type.symbols, 'class') + : new UnresolvedRelativeReference(varRef.document, varRef.node, type.symbols, 'class'); + + return resolveReference(varRef.document.project, typeRef, 'declaration'); +} diff --git a/server/src/analyzer.ts b/server/src/analyzer.ts index 789b0f8..84df3ac 100644 --- a/server/src/analyzer.ts +++ b/server/src/analyzer.ts @@ -46,10 +46,17 @@ import * as fs from 'node:fs/promises'; import * as fsSync from 'node:fs'; import * as url from 'node:url'; +import { + UnresolvedAbsoluteReference, + UnresolvedReference, + UnresolvedRelativeReference, +} from './analysis/reference'; +import resolveReference from './analysis/resolveReference'; import { ModelicaDocument, ModelicaLibrary, ModelicaProject } from './project'; +import { uriToPath } from './util'; +import * as TreeSitterUtil from './util/tree-sitter'; import { getAllDeclarationsInTree } from './util/declarations'; import { logger } from './util/logger'; -import { uriToPath } from './util'; export default class Analyzer { #project: ModelicaProject; @@ -110,7 +117,11 @@ export default class Analyzer { * @param text the modification * @param range range to update, or `undefined` to replace the whole file */ - public async updateDocument(uri: LSP.DocumentUri, text: string, range?: LSP.Range): Promise { + public async updateDocument( + uri: LSP.DocumentUri, + text: string, + range?: LSP.Range, + ): Promise { await this.#project.updateDocument(uriToPath(uri), text, range); } @@ -142,4 +153,178 @@ export default class Analyzer { return getAllDeclarationsInTree(tree, uri); } + + /** + * Finds the position of the declaration of the symbol at the given position. + * + * @param uri the opened document + * @param position the cursor position + * @returns a {@link LSP.LocationLink} to the symbol's declaration, or `null` + * if not found. + */ + public async findDeclaration( + uri: LSP.DocumentUri, + position: LSP.Position, + ): Promise { + const path = uriToPath(uri); + logger.debug( + `Searching for declaration of symbol at ${position.line + 1}:${ + position.character + 1 + } in '${path}'`, + ); + + const document = await this.#project.getDocument(path); + if (!document) { + logger.warn(`Couldn't find declaration: document not loaded.`); + return null; + } + + if (!document.tree.rootNode) { + logger.info(`Couldn't find declaration: document has no nodes.`); + return null; + } + + const reference = this.getReferenceAt(document, position); + if (!reference) { + logger.info(`Tried to find declaration in '${path}', but not hovering on any identifiers`); + return null; + } + + logger.debug( + `Searching for '${reference}' at ${position.line + 1}:${position.character + 1} in '${path}'`, + ); + + try { + const result = resolveReference(document.project, reference, 'declaration'); + if (!result) { + logger.debug(`Didn't find declaration of ${reference.symbols.join('.')}`); + return null; + } + + const link = TreeSitterUtil.createLocationLink(result.document, result.node); + logger.debug(`Found declaration of ${reference.symbols.join('.')}: `, link); + return link; + } catch (e: unknown) { + if (e instanceof Error) { + logger.debug('Caught exception: ', e.stack); + } else { + logger.debug(`Caught:`, e); + } + return null; + } + } + + /** + * Returns the reference at the document position, or `null` if no reference + * exists. + */ + private getReferenceAt( + document: ModelicaDocument, + position: LSP.Position, + ): UnresolvedReference | null { + function checkBeforeCursor(node: Parser.SyntaxNode): boolean { + if (node.startPosition.row < position.line) { + return true; + } + return ( + node.startPosition.row === position.line && node.startPosition.column <= position.character + ); + } + + const documentOffset = document.offsetAt(position); + + // First, check if this is a `type_specifier` or a `name`. + let hoveredType = this.findNodeAtPosition( + document.tree.rootNode, + documentOffset, + (node) => node.type === 'name', + ); + + if (hoveredType) { + if (hoveredType.parent?.type === 'type_specifier') { + hoveredType = hoveredType.parent; + } + + const declaredType = TreeSitterUtil.getTypeSpecifier(hoveredType); + const symbols = declaredType.symbolNodes.filter(checkBeforeCursor).map((node) => node.text); + + if (declaredType.isGlobal) { + return new UnresolvedAbsoluteReference(symbols, 'class'); + } else { + const startNode = this.findNodeAtPosition( + hoveredType, + documentOffset, + (node) => node.type === 'IDENT', + )!; + + return new UnresolvedRelativeReference(document, startNode, symbols, 'class'); + } + } + + // Next, check if this is a `component_reference`. + const hoveredComponentReference = this.findNodeAtPosition( + document.tree.rootNode, + documentOffset, + (node) => node.type === 'component_reference', + ); + if (hoveredComponentReference) { + // TODO: handle array indices + const componentReference = TreeSitterUtil.getComponentReference(hoveredComponentReference); + const symbols = componentReference.componentNodes + .filter(checkBeforeCursor) + .map((node) => node.text); + + if (componentReference.isGlobal) { + return new UnresolvedAbsoluteReference(symbols, 'variable'); + } else { + const startNode = this.findNodeAtPosition( + hoveredComponentReference, + documentOffset, + (node) => node.type === 'IDENT', + )!; + + return new UnresolvedRelativeReference(document, startNode, symbols, 'variable'); + } + } + + // Finally, give up and check if this is just an ident. + const startNode = this.findNodeAtPosition( + document.tree.rootNode, + documentOffset, + (node) => node.type === 'IDENT', + ); + if (startNode) { + return new UnresolvedRelativeReference(document, startNode, [startNode.text]); + } + + // We're not hovering over an identifier. + return null; + } + + /** + * Locates the first node at the given text position that matches the given + * `condition`, starting from the `rootNode`. + * + * Note: it is very important to have some kind of condition. If one tries to + * just accept the first node at that position, this function will always + * return the `rootNode` (or `undefined` if outside the node.) + * + * @param rootNode node to start searching from. parents/siblings of this node will be ignored + * @param offset the offset of the symbol from the start of the document + * @param condition the condition to check if a node is "good" + * @returns the node at the position, or `undefined` if none was found + */ + private findNodeAtPosition( + rootNode: Parser.SyntaxNode, + offset: number, + condition: (node: Parser.SyntaxNode) => boolean, + ): Parser.SyntaxNode | undefined { + // TODO: find the deepest node. findFirst doesn't work (maybe?) + const hoveredNode = TreeSitterUtil.findFirst(rootNode, (node) => { + const isInNode = offset >= node.startIndex && offset <= node.endIndex; + return isInNode && condition(node); + }); + + return hoveredNode ?? undefined; + } } diff --git a/server/src/server.ts b/server/src/server.ts index 5ac20d0..5a162aa 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -97,6 +97,8 @@ export class ModelicaServer { public capabilities(): LSP.ServerCapabilities { return { completionProvider: undefined, + declarationProvider: true, + definitionProvider: true, hoverProvider: false, signatureHelpProvider: undefined, documentSymbolProvider: true, @@ -121,6 +123,8 @@ export class ModelicaServer { connection.onShutdown(this.onShutdown.bind(this)); connection.onDidChangeTextDocument(this.onDidChangeTextDocument.bind(this)); connection.onDidChangeWatchedFiles(this.onDidChangeWatchedFiles.bind(this)); + connection.onDeclaration(this.onDeclaration.bind(this)); + connection.onDefinition(this.onDefinition.bind(this)); connection.onDocumentSymbol(this.onDocumentSymbol.bind(this)); } @@ -178,13 +182,81 @@ export class ModelicaServer { } } + // TODO: We currently treat goto declaration and goto definition the same, + // but there are probably some differences we need to handle. + // + // 1. inner/outer variables. Modelica allows the user to redeclare variables + // from enclosing classes to use them in inner classes. Goto Declaration + // should go to whichever declaration is in scope, while Goto Definition + // should go to the `outer` declaration. In the following example: + // + // model Outer + // model Inner + // inner Real shared; + // equation + // shared = ...; (A) + // end Inner; + // outer Real shared = 0; + // equation + // shared = ...; (B) + // end Outer; + // + // +-----+-------------+------------+ + // | Ref | Declaration | Definition | + // +-----+-------------+------------+ + // | A | inner | outer | + // | B | outer | outer | + // +-----+-------------+------------+ + // + // 2. extends_clause is weird. This is a valid class: + // + // class extends Foo; + // end Foo; + // + // What does this even mean? Is this a definition of Foo or a redeclaration of Foo? + // + // 3. Import aliases. Should this be considered to be a declaration of `Frobnicator`? + // + // import Frobnicator = Foo.Bar.Baz; + // + + private async onDeclaration(params: LSP.DeclarationParams): Promise { + logger.debug('onDeclaration'); + + const locationLink = await this.#analyzer.findDeclaration( + params.textDocument.uri, + params.position, + ); + if (locationLink == null) { + return []; + } + + return [locationLink]; + } + + private async onDefinition(params: LSP.DefinitionParams): Promise { + logger.debug('onDefinition'); + + const locationLink = await this.#analyzer.findDeclaration( + params.textDocument.uri, + params.position, + ); + if (locationLink == null) { + return []; + } + + return [locationLink]; + } + /** * Provide symbols defined in document. * * @param params Unused. * @returns Symbol information. */ - private async onDocumentSymbol(params: LSP.DocumentSymbolParams): Promise { + private async onDocumentSymbol( + params: LSP.DocumentSymbolParams, + ): Promise { // TODO: ideally this should return LSP.DocumentSymbol[] instead of LSP.SymbolInformation[] // which is a hierarchy of symbols. // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol diff --git a/server/src/util/tree-sitter.ts b/server/src/util/tree-sitter.ts index 601f632..2fdc943 100644 --- a/server/src/util/tree-sitter.ts +++ b/server/src/util/tree-sitter.ts @@ -44,6 +44,7 @@ import * as LSP from 'vscode-languageserver/node'; import { SyntaxNode } from 'web-tree-sitter'; import { logger } from './logger'; +import { TextDocument } from 'vscode-languageserver-textdocument'; /** * Recursively iterate over all nodes in a tree. @@ -128,6 +129,41 @@ export function isDefinition(n: SyntaxNode): boolean { } } +/** + * Tell if a node is a variable declaration. + * + * @param n Node of tree + * @returns `true` if node is a variable declaration, `false` otherwise. + */ +export function isVariableDeclaration(n: SyntaxNode): boolean { + switch (n.type) { + case 'component_clause': + case 'component_redeclaration': + return true; + case 'named_element': + return n.childForFieldName('classDefinition') == null; + default: + return false; + } +} + +/** + * Tell if a node is an element list. + * + * @param n Node of tree + * @returns `true` if node is an element list, `false` otherwise. + */ +export function isElementList(n: SyntaxNode): boolean { + switch (n.type) { + case 'element_list': + case 'public_element_list': + case 'protected_element_list': + return true; + default: + return false; + } +} + export function findParent( start: SyntaxNode, predicate: (n: SyntaxNode) => boolean, @@ -152,6 +188,176 @@ export function getIdentifier(start: SyntaxNode): string | undefined { return node?.text; } +/** + * Returns the identifier(s) declared by the given node, or `[]` if no + * identifiers are declared. + * + * Note: this does not return any identifiers that are declared "inside" of the + * node. For instance, calling `getDeclaredIdentifiers` on a class_definition + * will only return the name of the class. + * + * @param node The node to check. Must be a declaration. + * @returns The identifiers. + */ +export function getDeclaredIdentifiers(node: SyntaxNode): string[] { + if (node == null) { + throw new Error('getDeclaredIdentifiers called with null/undefined node'); + } + + // TODO: does this support all desired node types? Are we considering too many nodes? + switch (node.type) { + case 'declaration': + case 'derivative_class_specifier': + case 'enumeration_class_specifier': + case 'extends_class_specifier': + case 'long_class_specifier': + case 'short_class_specifier': + case 'enumeration_literal': + case 'for_index': + return [node.childForFieldName('identifier')!.text]; + case 'stored_definitions': + case 'component_list': + case 'enum_list': + case 'element_list': + case 'public_element_list': + case 'protected_element_list': + case 'for_indices': + return node.namedChildren.flatMap(getDeclaredIdentifiers); + case 'component_clause': + return getDeclaredIdentifiers(node.childForFieldName('componentDeclarations')!); + case 'component_declaration': + return getDeclaredIdentifiers(node.childForFieldName('declaration')!); + case 'component_redeclaration': + return getDeclaredIdentifiers(node.childForFieldName('componentClause')!); + case 'stored_definition': + return getDeclaredIdentifiers(node.childForFieldName('classDefinition')!); + case 'class_definition': + return getDeclaredIdentifiers(node.childForFieldName('classSpecifier')!); + case 'for_equation': + case 'for_statement': + return getDeclaredIdentifiers(node.childForFieldName('indices')!); + case 'named_element': { + const definition = + node.childForFieldName('classDefinition') ?? node.childForFieldName('componentClause')!; + return getDeclaredIdentifiers(definition); + } + default: + return []; + } +} + +export function hasIdentifier(node: SyntaxNode | null, identifier: string): boolean { + if (!node) { + return false; + } + + return getDeclaredIdentifiers(node).includes(identifier); +} + +export interface TypeSpecifier { + isGlobal: boolean; + symbols: string[]; + symbolNodes: SyntaxNode[]; +} + +export function getTypeSpecifier(node: SyntaxNode): TypeSpecifier { + switch (node.type) { + case 'type_specifier': { + const isGlobal = node.childForFieldName('global') !== null; + const name = node.childForFieldName('name')!; + const symbolNodes = getNameIdentifiers(name); + return { + isGlobal, + symbols: symbolNodes.map((id) => id.text), + symbolNodes, + }; + } + case 'name': { + const symbolNodes = getNameIdentifiers(node); + return { + isGlobal: false, + symbols: symbolNodes.map((id) => id.text), + symbolNodes, + }; + } + case 'IDENT': + return { + isGlobal: false, + symbols: [node.text], + symbolNodes: [node], + }; + default: { + const typeSpecifier = findFirst(node, (child) => child.type === 'type_specifier'); + if (typeSpecifier) { + return getTypeSpecifier(typeSpecifier); + } + + const name = findFirst(node, (child) => child.type === 'name'); + if (name) { + return getTypeSpecifier(name); + } + + throw new Error('Syntax node does not contain a type_specifier or name'); + } + } +} + +// TODO: this does not handle indexing arrays +export interface ComponentReference { + isGlobal: boolean; + components: string[]; + componentNodes: SyntaxNode[]; +} + +export function getComponentReference(node: SyntaxNode): ComponentReference { + switch (node.type) { + case 'component_reference': { + const isGlobal = node.childForFieldName('global') !== null; + const componentNodes = getNameIdentifiers(node); + + return { + isGlobal, + components: componentNodes.map((id) => id.text), + componentNodes, + }; + } + case 'IDENT': + return { + isGlobal: false, + components: [node.text], + componentNodes: [node], + }; + default: { + const componentRef = findFirst(node, (child) => child.type === 'component_reference'); + if (componentRef) { + return getComponentReference(componentRef); + } + + throw new Error('Syntax node does not contain a component_reference'); + } + } +} + +/** + * Converts a name `SyntaxNode` into an array of the `IDENT`s in that node. + */ +function getNameIdentifiers(nameNode: SyntaxNode): Parser.SyntaxNode[] { + if (nameNode.type !== 'name' && nameNode.type !== 'component_reference') { + throw new Error( + `Expected a 'name' or 'component_reference' node; got '${nameNode.type}' (${nameNode.text})`, + ); + } + + const identNode = nameNode.childForFieldName('identifier')!; + const qualifierNode = nameNode.childForFieldName('qualifier'); + if (qualifierNode) { + const qualifier = getNameIdentifiers(qualifierNode); + return [...qualifier, identNode]; + } else { + return [identNode]; + } +} + /** * Get class prefixes from `class_definition` node. * @@ -178,3 +384,29 @@ export function positionToPoint(position: LSP.Position): Parser.Point { export function pointToPosition(point: Parser.Point): LSP.Position { return { line: point.row, character: point.column }; } + +export function createLocationLink( + document: TextDocument, + node: Parser.SyntaxNode, +): LSP.LocationLink; +export function createLocationLink( + documentUri: LSP.DocumentUri, + node: Parser.SyntaxNode, +): LSP.LocationLink; +export function createLocationLink( + document: TextDocument | LSP.DocumentUri, + node: Parser.SyntaxNode, +): LSP.LocationLink { + // TODO: properly set targetSelectionRange (e.g. the name of a function or variable). + return { + targetUri: typeof document === 'string' ? document : document.uri, + targetRange: { + start: pointToPosition(node.startPosition), + end: pointToPosition(node.endPosition), + }, + targetSelectionRange: { + start: pointToPosition(node.startPosition), + end: pointToPosition(node.endPosition), + }, + }; +} From ebb2fe5e0b6f76f6744263499a1b81d614c3dd43 Mon Sep 17 00:00:00 2001 From: PaddiM8 Date: Mon, 17 Jun 2024 16:22:10 +0200 Subject: [PATCH 2/3] Add tests for goto declaration Co-authored-by: Evan Hedbor --- client/src/test/gotoDeclaration.test.ts | 61 +++++++ package.json | 2 +- server/src/analysis/reference.ts | 42 +++++ server/src/analysis/resolveReference.ts | 7 +- .../analysis/test/TestLibrary/Constants.mo | 6 + .../test/TestLibrary/TestPackage/TestClass.mo | 9 + .../test/TestLibrary/TestPackage/package.mo | 4 + .../src/analysis/test/TestLibrary/package.mo | 3 + .../analysis/test/resolveReference.test.ts | 168 ++++++++++++++++++ server/src/project/library.ts | 32 ++-- server/src/project/project.ts | 33 +++- 11 files changed, 345 insertions(+), 22 deletions(-) create mode 100644 client/src/test/gotoDeclaration.test.ts create mode 100644 server/src/analysis/test/TestLibrary/Constants.mo create mode 100644 server/src/analysis/test/TestLibrary/TestPackage/TestClass.mo create mode 100644 server/src/analysis/test/TestLibrary/TestPackage/package.mo create mode 100644 server/src/analysis/test/TestLibrary/package.mo create mode 100644 server/src/analysis/test/resolveReference.test.ts diff --git a/client/src/test/gotoDeclaration.test.ts b/client/src/test/gotoDeclaration.test.ts new file mode 100644 index 0000000..bdda157 --- /dev/null +++ b/client/src/test/gotoDeclaration.test.ts @@ -0,0 +1,61 @@ +/* + * This file is part of OpenModelica. + * + * Copyright (c) 1998-2024, Open Source Modelica Consortium (OSMC), + * c/o Linköpings universitet, Department of Computer and Information Science, + * SE-58183 Linköping, Sweden. + * + * All rights reserved. + * + * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR + * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. + * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES + * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL + * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. + * + * The OpenModelica software and the OSMC (Open Source Modelica Consortium) + * Public License (OSMC-PL) are obtained from OSMC, either from the above + * address, from the URLs: + * http://www.openmodelica.org or + * https://github.com/OpenModelica/ or + * http://www.ida.liu.se/projects/OpenModelica, + * and in the OpenModelica distribution. + * + * GNU AGPL version 3 is obtained from: + * https://www.gnu.org/licenses/licenses.html#GPL + * + * This program is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH + * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. + * + * See the full OSMC Public License conditions for more details. + * + */ + +import * as vscode from 'vscode'; +import * as assert from 'assert'; +import { getDocUri, activate } from './helper'; + +suite('Goto Declaration', () => { + test('onDeclaration()', async () => { + const docUri = getDocUri('MyLibrary.mo'); + await activate(docUri); + + const position = new vscode.Position(4, 18); + const actualLocations = await vscode.commands.executeCommand( + 'vscode.executeDeclarationProvider', + docUri, + position, + ); + + assert.strictEqual(actualLocations.length, 1); + + const actualLocation = actualLocations[0]; + assert.strictEqual(actualLocation.targetUri.toString(), docUri.toString()); + assert.strictEqual(actualLocation.targetRange.start.line, 2); + assert.strictEqual(actualLocation.targetRange.start.character, 4); + assert.strictEqual(actualLocation.targetRange.end.line, 2); + assert.strictEqual(actualLocation.targetRange.end.character, 37); + }); +}); diff --git a/package.json b/package.json index 4d9d769..6ae9c64 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "test:e2e": "run-script-os", "test:e2e:win32": "npm run test-compile && powershell -File ./scripts/e2e.ps1", "test:e2e:default": "npm run test-compile && sh ./scripts/e2e.sh", - "test:server": "cd server && npx mocha -r ts-node/register src/test/**/*.test.ts src/project/test/**/*.test.ts src/util/test/**/*.test.ts", + "test:server": "cd server && npx mocha -r ts-node/register src/test/**/*.test.ts src/project/test/**/*.test.ts src/util/test/**/*.test.ts src/analysis/test/**/*.test.ts", "all": "npm run postinstall && npm run esbuild && npm run lint && npm run test:server && npm run test:e2e && npm run vscode:prepublish" }, "devDependencies": { diff --git a/server/src/analysis/reference.ts b/server/src/analysis/reference.ts index 5f9f598..165bdf7 100644 --- a/server/src/analysis/reference.ts +++ b/server/src/analysis/reference.ts @@ -21,6 +21,8 @@ export abstract class BaseUnresolvedReference { } public abstract isAbsolute(): this is UnresolvedAbsoluteReference; + + public abstract equals(other: unknown): boolean; } export class UnresolvedRelativeReference extends BaseUnresolvedReference { @@ -49,6 +51,20 @@ export class UnresolvedRelativeReference extends BaseUnresolvedReference { return false; } + public equals(other: unknown): boolean { + if (!(other instanceof UnresolvedRelativeReference)) { + return false; + } + + return ( + this.document.uri === other.document.uri && + this.node.equals(other.node) && + this.symbols.length === other.symbols.length && + this.symbols.every((s, i) => s === other.symbols[i]) && + this.kind === other.kind + ); + } + public toString(): string { const start = this.node.startPosition; return ( @@ -71,6 +87,18 @@ export class UnresolvedAbsoluteReference extends BaseUnresolvedReference { return true; } + public equals(other: unknown): boolean { + if (!(other instanceof UnresolvedAbsoluteReference)) { + return false; + } + + return ( + this.symbols.length === other.symbols.length && + this.symbols.every((s, i) => s === other.symbols[i]) && + this.kind === other.kind + ); + } + public toString(): string { return ( `UnresolvedReference { ` + @@ -123,6 +151,20 @@ export class ResolvedReference { this.kind = kind; } + public equals(other: unknown): boolean { + if (!(other instanceof ResolvedReference)) { + return false; + } + + return ( + this.document.uri === other.document.uri && + this.node.equals(other.node) && + this.symbols.length === other.symbols.length && + this.symbols.every((s, i) => s === other.symbols[i]) && + this.kind === other.kind + ); + } + public toString(): string { return `Reference { symbols: .${this.symbols.join('.')}, kind: ${this.kind} }`; } diff --git a/server/src/analysis/resolveReference.ts b/server/src/analysis/resolveReference.ts index 0948f4b..cf40c3d 100644 --- a/server/src/analysis/resolveReference.ts +++ b/server/src/analysis/resolveReference.ts @@ -405,6 +405,7 @@ function resolveAbsoluteReference( logger.debug(`Resolving ${reference}`); + logger.debug(project.libraries.map((x) => x.name + ' | ' + x.path).join('\n\t')); const library = project.libraries.find((lib) => lib.name === reference.symbols[0]); if (library == null) { logger.debug(`Couldn't find library: ${reference.symbols[0]}`); @@ -453,7 +454,11 @@ function resolveNext( ): ResolvedReference | null { // If at the root level, find the root package if (!parentReference) { - const documentPath = path.join(library.path, 'package.mo'); + let documentPath = path.join(library.path, 'package.mo'); + if (!fs.existsSync(documentPath)) { + documentPath = path.join(library.path, library.name + '.mo'); + } + const [document, packageClass] = getPackageClassFromFilePath(library, documentPath, nextSymbol); if (!document || !packageClass) { logger.debug(`Couldn't find package class: ${nextSymbol} in ${documentPath}`); diff --git a/server/src/analysis/test/TestLibrary/Constants.mo b/server/src/analysis/test/TestLibrary/Constants.mo new file mode 100644 index 0000000..1d36768 --- /dev/null +++ b/server/src/analysis/test/TestLibrary/Constants.mo @@ -0,0 +1,6 @@ +within TestLibrary; + +package Constants + constant Real e = Modelica.Math.exp(1.0); + constant Real pi = 2 * Modelica.Math.asin(1.0); +end Constants; diff --git a/server/src/analysis/test/TestLibrary/TestPackage/TestClass.mo b/server/src/analysis/test/TestLibrary/TestPackage/TestClass.mo new file mode 100644 index 0000000..9c0f7df --- /dev/null +++ b/server/src/analysis/test/TestLibrary/TestPackage/TestClass.mo @@ -0,0 +1,9 @@ +within TestLibrary.TestPackage; + +import TestLibrary.Constants.pi; + +function TestClass + input Real twoE = 2 * Constants.e; + input Real tau = 2 * pi; + input Real notTau = tau / twoE; +end TestClass; diff --git a/server/src/analysis/test/TestLibrary/TestPackage/package.mo b/server/src/analysis/test/TestLibrary/TestPackage/package.mo new file mode 100644 index 0000000..80e8ffa --- /dev/null +++ b/server/src/analysis/test/TestLibrary/TestPackage/package.mo @@ -0,0 +1,4 @@ +within TestLibrary; + +package TestPackage +end TestPackage; diff --git a/server/src/analysis/test/TestLibrary/package.mo b/server/src/analysis/test/TestLibrary/package.mo new file mode 100644 index 0000000..8493bc1 --- /dev/null +++ b/server/src/analysis/test/TestLibrary/package.mo @@ -0,0 +1,3 @@ +package TestLibrary + annotation(version="1.0.0"); +end TestLibrary; diff --git a/server/src/analysis/test/resolveReference.test.ts b/server/src/analysis/test/resolveReference.test.ts new file mode 100644 index 0000000..2beb8d3 --- /dev/null +++ b/server/src/analysis/test/resolveReference.test.ts @@ -0,0 +1,168 @@ +/* + * This file is part of OpenModelica. + * + * Copyright (c) 1998-2024, Open Source Modelica Consortium (OSMC), + * c/o Linköpings universitet, Department of Computer and Information Science, + * SE-58183 Linköping, Sweden. + * + * All rights reserved. + * + * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR + * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. + * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES + * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL + * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. + * + * The OpenModelica software and the OSMC (Open Source Modelica Consortium) + * Public License (OSMC-PL) are obtained from OSMC, either from the above + * address, from the URLs: + * http://www.openmodelica.org or + * https://github.com/OpenModelica/ or + * http://www.ida.liu.se/projects/OpenModelica, + * and in the OpenModelica distribution. + * + * GNU AGPL version 3 is obtained from: + * https://www.gnu.org/licenses/licenses.html#GPL + * + * This program is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH + * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. + * + * See the full OSMC Public License conditions for more details. + * + */ + +import Parser from 'web-tree-sitter'; +import assert from 'node:assert/strict'; +import path from 'node:path'; +import { ModelicaProject, ModelicaLibrary, ModelicaDocument } from '../../project'; +import { initializeParser } from '../../parser'; +import resolveReference from '../resolveReference'; +import { + UnresolvedAbsoluteReference, + UnresolvedRelativeReference, + ResolvedReference, +} from '../reference'; +import * as TreeSitterUtil from '../../util/tree-sitter'; + +const TEST_LIBRARY_PATH = path.join(__dirname, 'TestLibrary'); +const TEST_CLASS_PATH = path.join(TEST_LIBRARY_PATH, 'TestPackage', 'TestClass.mo'); +const CONSTANTS_PATH = path.join(TEST_LIBRARY_PATH, 'Constants.mo'); + +describe('resolveReference', () => { + let project: ModelicaProject; + + beforeEach(async () => { + const parser = await initializeParser(); + project = new ModelicaProject(parser); + project.addLibrary(await ModelicaLibrary.load(project, TEST_LIBRARY_PATH, true)); + }); + + it('should resolve absolute references to classes', async () => { + const unresolved = new UnresolvedAbsoluteReference(['TestLibrary', 'TestPackage', 'TestClass']); + const resolved = resolveReference(project, unresolved, 'declaration'); + + const resolvedDocument = await project.getDocument(TEST_CLASS_PATH); + assert(resolvedDocument !== undefined); + + // Get node declarting `TestClass` + const resolvedNode = TreeSitterUtil.findFirst( + resolvedDocument.tree.rootNode, + (node) => + node.type === 'class_definition' && TreeSitterUtil.getIdentifier(node) === 'TestClass', + )!; + const resolvedSymbols = ['TestLibrary', 'TestPackage', 'TestClass']; + + assert( + resolved?.equals( + new ResolvedReference(resolvedDocument, resolvedNode, resolvedSymbols, 'class'), + ), + ); + }); + + it('should resolve absolute references to variables', async () => { + const unresolved = new UnresolvedAbsoluteReference(['TestLibrary', 'Constants', 'e']); + const resolved = resolveReference(project, unresolved, 'declaration'); + + const resolvedDocument = (await project.getDocument(CONSTANTS_PATH))!; + + // Get the node declaring `e` + const resolvedNode = TreeSitterUtil.findFirst( + resolvedDocument.tree.rootNode, + (node) => + node.type === 'component_clause' && TreeSitterUtil.getDeclaredIdentifiers(node)[0] === 'e', + )!; + const resolvedSymbols = ['TestLibrary', 'Constants', 'e']; + + assert( + resolved?.equals( + new ResolvedReference(resolvedDocument, resolvedNode, resolvedSymbols, 'variable'), + ), + ); + }); + + it('should resolve relative references to locals', async () => { + const document = (await project.getDocument(TEST_CLASS_PATH))!; + const unresolvedNode = TreeSitterUtil.findFirst( + document.tree.rootNode, + (node) => node.startPosition.row === 7 && node.startPosition.column === 21, + )!; + const unresolved = new UnresolvedRelativeReference(document, unresolvedNode, ['tau']); + const resolved = resolveReference(project, unresolved, 'declaration'); + + // the resolved node is the declaration of tau + // `input Real tau = 2 * pi;` + const resolvedNode = TreeSitterUtil.findFirst( + document.tree.rootNode, + (node) => + node.type === 'component_clause' && + TreeSitterUtil.getDeclaredIdentifiers(node)[0] === 'tau', + )!; + + assert( + resolved?.equals( + new ResolvedReference( + document, + resolvedNode, + ['TestLibrary', 'TestPackage', 'TestClass', 'tau'], + 'variable', + ), + ), + ); + }); + + it('should resolve relative references to globals', async () => { + // input Real twoE = 2 * Constants.e; + // ^ 5:33 + const unresolvedDocument = (await project.getDocument(TEST_CLASS_PATH))!; + const unresolvedNode = TreeSitterUtil.findFirst( + unresolvedDocument.tree.rootNode, + (node) => node.startPosition.row === 5 && node.startPosition.column === 33, + )!; + const unresolved = new UnresolvedRelativeReference(unresolvedDocument, unresolvedNode, [ + 'Constants', + 'e', + ]); + const resolved = resolveReference(project, unresolved, 'declaration'); + + const resolvedDocument = (await project.getDocument(CONSTANTS_PATH))!; + // Get the node declaring `e` + const resolvedNode = TreeSitterUtil.findFirst( + resolvedDocument.tree.rootNode, + (node) => + node.type === 'component_clause' && TreeSitterUtil.getDeclaredIdentifiers(node)[0] === 'e', + )!; + + assert( + resolved?.equals( + new ResolvedReference( + resolvedDocument, + resolvedNode, + ['TestLibrary', 'Constants', 'e'], + 'variable', + ), + ), + ); + }); +}); diff --git a/server/src/project/library.ts b/server/src/project/library.ts index fb1175d..dade9bc 100644 --- a/server/src/project/library.ts +++ b/server/src/project/library.ts @@ -33,26 +33,32 @@ * */ -import * as LSP from "vscode-languageserver"; -import * as fsWalk from "@nodelib/fs.walk"; -import * as path from "node:path"; -import * as util from "node:util"; +import * as LSP from 'vscode-languageserver'; +import * as fsWalk from '@nodelib/fs.walk'; +import * as path from 'node:path'; +import * as util from 'node:util'; import { logger } from '../util/logger'; -import { ModelicaDocument } from "./document"; -import { ModelicaProject } from "./project"; +import { ModelicaDocument } from './document'; +import { ModelicaProject } from './project'; export class ModelicaLibrary { readonly #project: ModelicaProject; readonly #documents: Map; readonly #isWorkspace: boolean; + readonly #name: string; #path: string; - public constructor(project: ModelicaProject, libraryPath: string, isWorkspace: boolean) { + public constructor( + project: ModelicaProject, + libraryPath: string, + isWorkspace: boolean, + name?: string, + ) { this.#project = project; - this.#path = libraryPath, - this.#documents = new Map(); + (this.#path = libraryPath), (this.#documents = new Map()); this.#isWorkspace = isWorkspace; + this.#name = name ?? path.basename(this.path); } /** @@ -71,7 +77,11 @@ export class ModelicaLibrary { logger.info(`Loading ${isWorkspace ? 'workspace' : 'library'} at '${libraryPath}'...`); const library = new ModelicaLibrary(project, libraryPath, isWorkspace); - const workspaceRootDocument = await ModelicaDocument.load(project, library, path.join(libraryPath, 'package.mo')); + const workspaceRootDocument = await ModelicaDocument.load( + project, + library, + path.join(libraryPath, 'package.mo'), + ); // Find the root path of the library and update library.#path. // It might have been set incorrectly if we opened a child folder. @@ -96,7 +106,7 @@ export class ModelicaLibrary { } public get name(): string { - return path.basename(this.path); + return this.#name; } public get path(): string { diff --git a/server/src/project/project.ts b/server/src/project/project.ts index 53adec4..30abe0d 100644 --- a/server/src/project/project.ts +++ b/server/src/project/project.ts @@ -44,12 +44,12 @@ import { logger } from '../util/logger'; /** Options for {@link ModelicaProject.getDocument} */ export interface GetDocumentOptions { - /** - * `true` to try loading the document from disk if it is not already loaded. - * - * Default value: `true`. - */ - load?: boolean, + /** + * `true` to try loading the document from disk if it is not already loaded. + * + * Default value: `true`. + */ + load?: boolean; } export class ModelicaProject { @@ -78,7 +78,10 @@ export class ModelicaProject { * @param options * @returns the document, or `undefined` if no such document exists */ - public async getDocument(documentPath: string, options?: GetDocumentOptions): Promise { + public async getDocument( + documentPath: string, + options?: GetDocumentOptions, + ): Promise { let loadedDocument: ModelicaDocument | undefined = undefined; for (const library of this.#libraries) { loadedDocument = library.documents.get(documentPath); @@ -137,8 +140,16 @@ export class ModelicaProject { // If the document doesn't belong to a library, it could still be loaded // as a standalone document if it has an empty or non-existent within clause - const document = await ModelicaDocument.load(this, null, documentPath); + const standaloneName = path.basename(documentPath).split('.')[0]; + const standaloneLibrary = new ModelicaLibrary( + this, + path.dirname(documentPath), + false, + standaloneName, + ); + const document = await ModelicaDocument.load(this, standaloneLibrary, documentPath); if (document.within.length === 0) { + this.addLibrary(standaloneLibrary); logger.debug(`Added document: ${documentPath}`); return document; } @@ -155,7 +166,11 @@ export class ModelicaProject { * @param text the modification * @returns if the document was updated */ - public async updateDocument(documentPath: string, text: string, range?: LSP.Range): Promise { + public async updateDocument( + documentPath: string, + text: string, + range?: LSP.Range, + ): Promise { logger.debug(`Updating document at '${documentPath}'...`); const doc = await this.getDocument(documentPath, { load: true }); From 319129e840e74883539996bca61dcc165f9d87f2 Mon Sep 17 00:00:00 2001 From: AnHeuermann <38031952+AnHeuermann@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:40:38 +0200 Subject: [PATCH 3/3] Fixing library name for libs with version in path - Adding copyright - Updating README to showecase goto-declaration - Fixing ModelicaLibrary to work for e.g. "path/to/Modelica 4.0.0+main.om" --- README.md | 4 ++ ...rging.jpg => Modelica_Language_margin.jpg} | Bin images/goto_declaration_demo.png | Bin 0 -> 91530 bytes package.json | 2 +- server/src/analysis/reference.ts | 35 ++++++++++++++++++ server/src/analysis/resolveReference.ts | 35 ++++++++++++++++++ .../analysis/test/resolveReference.test.ts | 2 +- server/src/project/library.ts | 3 +- .../HalfAdder.mo | 0 .../package.mo | 0 server/src/project/test/project.test.ts | 3 +- 11 files changed, 80 insertions(+), 4 deletions(-) rename images/{Modelica_Language_marging.jpg => Modelica_Language_margin.jpg} (100%) create mode 100644 images/goto_declaration_demo.png rename server/src/project/test/{TestLibrary => TestLibrary 1.0.0}/HalfAdder.mo (100%) rename server/src/project/test/{TestLibrary => TestLibrary 1.0.0}/package.mo (100%) diff --git a/README.md b/README.md index 07debb0..b6b21f7 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ features: ![Outline](images/outline_demo.png) + - Goto declarations. + + ![Goto Declaration](images/goto_declaration_demo.png) + ## Installation ### Via Marketplace diff --git a/images/Modelica_Language_marging.jpg b/images/Modelica_Language_margin.jpg similarity index 100% rename from images/Modelica_Language_marging.jpg rename to images/Modelica_Language_margin.jpg diff --git a/images/goto_declaration_demo.png b/images/goto_declaration_demo.png new file mode 100644 index 0000000000000000000000000000000000000000..71d57e4797cc7bf85430e3d48575b5a132b6692f GIT binary patch literal 91530 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

5M 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%0DWeYjRi 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 z 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 6ae9c64..e043ab8 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "language-server" ], "homepage": "https://github.com/OpenModelica/modelica-language-server", - "icon": "images/Modelica_Language_marging.jpg", + "icon": "images/Modelica_Language_margin.jpg", "bugs": "https://github.com/OpenModelica/modelica-language-server/issues", "engines": { "vscode": "^1.75.0" diff --git a/server/src/analysis/reference.ts b/server/src/analysis/reference.ts index 165bdf7..0592001 100644 --- a/server/src/analysis/reference.ts +++ b/server/src/analysis/reference.ts @@ -1,3 +1,38 @@ +/* + * This file is part of OpenModelica. + * + * Copyright (c) 1998-2024, Open Source Modelica Consortium (OSMC), + * c/o Linköpings universitet, Department of Computer and Information Science, + * SE-58183 Linköping, Sweden. + * + * All rights reserved. + * + * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR + * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. + * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES + * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL + * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. + * + * The OpenModelica software and the OSMC (Open Source Modelica Consortium) + * Public License (OSMC-PL) are obtained from OSMC, either from the above + * address, from the URLs: + * http://www.openmodelica.org or + * https://github.com/OpenModelica/ or + * http://www.ida.liu.se/projects/OpenModelica, + * and in the OpenModelica distribution. + * + * GNU AGPL version 3 is obtained from: + * https://www.gnu.org/licenses/licenses.html#GPL + * + * This program is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH + * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. + * + * See the full OSMC Public License conditions for more details. + * + */ + import { ModelicaDocument } from '../project/document'; import Parser from 'web-tree-sitter'; diff --git a/server/src/analysis/resolveReference.ts b/server/src/analysis/resolveReference.ts index cf40c3d..0be1566 100644 --- a/server/src/analysis/resolveReference.ts +++ b/server/src/analysis/resolveReference.ts @@ -1,3 +1,38 @@ +/* + * This file is part of OpenModelica. + * + * Copyright (c) 1998-2024, Open Source Modelica Consortium (OSMC), + * c/o Linköpings universitet, Department of Computer and Information Science, + * SE-58183 Linköping, Sweden. + * + * All rights reserved. + * + * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR + * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. + * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES + * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL + * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. + * + * The OpenModelica software and the OSMC (Open Source Modelica Consortium) + * Public License (OSMC-PL) are obtained from OSMC, either from the above + * address, from the URLs: + * http://www.openmodelica.org or + * https://github.com/OpenModelica/ or + * http://www.ida.liu.se/projects/OpenModelica, + * and in the OpenModelica distribution. + * + * GNU AGPL version 3 is obtained from: + * https://www.gnu.org/licenses/licenses.html#GPL + * + * This program is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH + * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. + * + * See the full OSMC Public License conditions for more details. + * + */ + import Parser from 'web-tree-sitter'; import * as fs from 'node:fs'; import * as path from 'node:path'; diff --git a/server/src/analysis/test/resolveReference.test.ts b/server/src/analysis/test/resolveReference.test.ts index 2beb8d3..d128e97 100644 --- a/server/src/analysis/test/resolveReference.test.ts +++ b/server/src/analysis/test/resolveReference.test.ts @@ -66,7 +66,7 @@ describe('resolveReference', () => { const resolvedDocument = await project.getDocument(TEST_CLASS_PATH); assert(resolvedDocument !== undefined); - // Get node declarting `TestClass` + // Get node declaring `TestClass` const resolvedNode = TreeSitterUtil.findFirst( resolvedDocument.tree.rootNode, (node) => diff --git a/server/src/project/library.ts b/server/src/project/library.ts index dade9bc..ab02273 100644 --- a/server/src/project/library.ts +++ b/server/src/project/library.ts @@ -58,7 +58,8 @@ export class ModelicaLibrary { this.#project = project; (this.#path = libraryPath), (this.#documents = new Map()); this.#isWorkspace = isWorkspace; - this.#name = name ?? path.basename(this.path); + // Path basename could contain version seperated by whitespace + this.#name = name ?? path.basename(this.path).split(/\s/)[0]; } /** diff --git a/server/src/project/test/TestLibrary/HalfAdder.mo b/server/src/project/test/TestLibrary 1.0.0/HalfAdder.mo similarity index 100% rename from server/src/project/test/TestLibrary/HalfAdder.mo rename to server/src/project/test/TestLibrary 1.0.0/HalfAdder.mo diff --git a/server/src/project/test/TestLibrary/package.mo b/server/src/project/test/TestLibrary 1.0.0/package.mo similarity index 100% rename from server/src/project/test/TestLibrary/package.mo rename to server/src/project/test/TestLibrary 1.0.0/package.mo diff --git a/server/src/project/test/project.test.ts b/server/src/project/test/project.test.ts index 9a75fd3..86a2dac 100644 --- a/server/src/project/test/project.test.ts +++ b/server/src/project/test/project.test.ts @@ -39,7 +39,7 @@ import assert from 'node:assert/strict'; import path from 'node:path'; import { initializeParser } from '../../parser'; -const TEST_LIBRARY_PATH = path.join(__dirname, 'TestLibrary'); +const TEST_LIBRARY_PATH = path.join(__dirname, 'TestLibrary 1.0.0'); const TEST_PACKAGE_PATH = path.join(TEST_LIBRARY_PATH, 'package.mo'); const TEST_CLASS_PATH = path.join(TEST_LIBRARY_PATH, 'HalfAdder.mo'); @@ -81,6 +81,7 @@ describe('ModelicaProject', () => { it('should add the library', () => { assert.equal(project.libraries.length, 1); assert.equal(project.libraries[0], library); + assert.equal(project.libraries[0].name, "TestLibrary") }); it('should add all the documents in the library', async () => {