Skip to content

Commit

Permalink
Add tests for goto declaration
Browse files Browse the repository at this point in the history
Co-authored-by: Evan Hedbor <[email protected]>
  • Loading branch information
PaddiM8 and ehedbor committed Jun 17, 2024
1 parent dd90bfc commit ebb2fe5
Show file tree
Hide file tree
Showing 11 changed files with 345 additions and 22 deletions.
61 changes: 61 additions & 0 deletions client/src/test/gotoDeclaration.test.ts
Original file line number Diff line number Diff line change
@@ -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.LocationLink[]>(
'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);
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
42 changes: 42 additions & 0 deletions server/src/analysis/reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 (
Expand All @@ -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 { ` +
Expand Down Expand Up @@ -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: <global>.${this.symbols.join('.')}, kind: ${this.kind} }`;
}
Expand Down
7 changes: 6 additions & 1 deletion server/src/analysis/resolveReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]}`);
Expand Down Expand Up @@ -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}`);
Expand Down
6 changes: 6 additions & 0 deletions server/src/analysis/test/TestLibrary/Constants.mo
Original file line number Diff line number Diff line change
@@ -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;
9 changes: 9 additions & 0 deletions server/src/analysis/test/TestLibrary/TestPackage/TestClass.mo
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 4 additions & 0 deletions server/src/analysis/test/TestLibrary/TestPackage/package.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
within TestLibrary;

package TestPackage
end TestPackage;
3 changes: 3 additions & 0 deletions server/src/analysis/test/TestLibrary/package.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package TestLibrary
annotation(version="1.0.0");
end TestLibrary;
168 changes: 168 additions & 0 deletions server/src/analysis/test/resolveReference.test.ts
Original file line number Diff line number Diff line change
@@ -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',
),
),
);
});
});
Loading

0 comments on commit ebb2fe5

Please sign in to comment.