Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugins on get references #620

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 9 additions & 23 deletions src/LanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type {
CompletionItem,
Connection,
DidChangeWatchedFilesParams,
Hover,
InitializeParams,
ServerCapabilities,
TextDocumentPositionParams,
Expand Down Expand Up @@ -874,24 +873,10 @@ export class LanguageServer {

let pathAbsolute = util.uriToPath(params.textDocument.uri);
let workspaces = this.getWorkspaces();
let hovers = await Promise.all(
Array.prototype.concat.call([],
workspaces.map(async (x) => x.builder.program.getHover(pathAbsolute, params.position))
)
) as Hover[];
let hovers = workspaces.map((x) => x.builder.program.getHover(pathAbsolute, params.position));

//return the first non-falsey hover. TODO is there a way to handle multiple hover results?
let hover = hovers.filter((x) => !!x)[0];

//TODO improve this to support more than just .brs files
if (hover?.contents) {
//create fenced code block to get colorization
hover.contents = {
//TODO - make the program.getHover call figure out what language this is for
language: 'brightscript',
value: hover.contents as string
};
}
return hover;
}

Expand Down Expand Up @@ -1055,13 +1040,14 @@ export class LanguageServer {
const position = params.position;
const pathAbsolute = util.uriToPath(params.textDocument.uri);

const results = util.flatMap(
await Promise.all(this.getWorkspaces().map(workspace => {
return workspace.builder.program.getReferences(pathAbsolute, position);
})),
c => c
);
return results.filter((r) => r);
const result = [];
for (const workspace of this.getWorkspaces()) {
result.push(
...workspace.builder.program.getReferences(pathAbsolute, position)
);
}
//remove falsey references
return result.filter(r => !!r);
}

private diagnosticCollection = new DiagnosticCollection();
Expand Down
9 changes: 7 additions & 2 deletions src/PluginInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ export default class PluginInterface<T extends CompilerPlugin = CompilerPlugin>
for (let plugin of this.plugins) {
if ((plugin as any)[event]) {
try {
this.logger.time(LogLevel.debug, [plugin.name, event], () => {
(plugin as any)[event](...args);
const returnValue = this.logger.time(LogLevel.debug, [plugin.name, event], () => {
return (plugin as any)[event](...args);
});

//plugins can short-circuit the event by returning `false`
if (returnValue === false) {
return;
}
} catch (err) {
this.logger.error(`Error when calling plugin ${plugin.name}.${event}:`, err);
}
Expand Down
45 changes: 30 additions & 15 deletions src/Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Scope } from './Scope';
import { DiagnosticMessages } from './DiagnosticMessages';
import { BrsFile } from './files/BrsFile';
import { XmlFile } from './files/XmlFile';
import type { BsDiagnostic, File, FileReference, FileObj, BscFile } from './interfaces';
import type { BsDiagnostic, File, FileReference, FileObj, BscFile, OnGetHoverEvent, OnGetReferencesEvent } from './interfaces';
import { standardizePath as s, util } from './util';
import { XmlScope } from './XmlScope';
import { DiagnosticFilterer } from './DiagnosticFilterer';
Expand Down Expand Up @@ -802,14 +802,22 @@ export class Program {
}
}

public getHover(pathAbsolute: string, position: Position) {
//find the file
let file = this.getFile(pathAbsolute);
if (!file) {
return null;
/**
* Get hover information for a file and position
*/
public getHover(srcPath: string, position: Position) {
let file = this.getFile(srcPath);
if (file) {
const event = {
program: this,
file: file,
position: position,
scopes: this.getScopesForFile(file),
hover: null
} as OnGetHoverEvent;
this.plugins.emit('onGetHover', event);
return event.hover;
}

return Promise.resolve(file.getHover(position));
}

/**
Expand Down Expand Up @@ -1067,14 +1075,21 @@ export class Program {

}

public getReferences(pathAbsolute: string, position: Position) {
//find the file
let file = this.getFile(pathAbsolute);
if (!file) {
return null;
public getReferences(srcPath: string, position: Position) {
let file = this.getFile(srcPath);
if (file) {
const event = {
program: this,
file: file,
position: position,
scopes: this.getScopesForFile(file),
references: []
} as OnGetReferencesEvent;
this.plugins.emit('onGetReferences', event);
return event.references ?? [];
} else {
return [];
}

return file.getReferences(position);
}

/**
Expand Down
14 changes: 12 additions & 2 deletions src/bscPlugin/BscPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import type { OnGetCodeActionsEvent, CompilerPlugin } from '../interfaces';
import type { OnGetCodeActionsEvent, CompilerPlugin, OnGetHoverEvent, OnGetReferencesEvent } from '../interfaces';
import { CodeActionsProcessor } from './codeActions/CodeActionsProcessor';
import { HoverProcessor } from './hover/HoverProcessor';
import { ReferencesProcessor } from './references/ReferencesProcessor';

export class BscPlugin implements CompilerPlugin {
public name = 'BscPlugin';

public onGetCodeActions(event: OnGetCodeActionsEvent) {
new CodeActionsProcessor(event).process();
return new CodeActionsProcessor(event).process();
}

public onGetHover(event: OnGetHoverEvent) {
return new HoverProcessor(event).process();
}

public onGetReferences(event: OnGetReferencesEvent) {
return new ReferencesProcessor(event).process();
}
}
157 changes: 157 additions & 0 deletions src/bscPlugin/hover/HoverProcessor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { expect } from 'chai';
import { Program } from '../../Program';
import util, { standardizePath as s } from '../../util';
import { createSandbox } from 'sinon';
let sinon = createSandbox();

let rootDir = s`${process.cwd()}/.tmp/rootDir`;

describe('HoverProcessor', () => {
let program: Program;
beforeEach(() => {
program = new Program({ rootDir: rootDir, sourceMap: true });
});
afterEach(() => {
sinon.restore();
program.dispose();
});

it('does not short-circuit the event since our plugin is the base plugin', () => {
const mock = sinon.mock();
program.plugins.add({
name: 'test-plugin',
onGetHover: mock
});
const file = program.addOrReplaceFile('source/main.brs', `
sub main()
end sub
`);
//get the hover
program.getHover(file.pathAbsolute, util.createPosition(1, 20));
//the onGetHover function from `test-plugin` should always get called because
//BscPlugin should never short-circuit the event
expect(mock.called).to.be.true;
});

describe('BrsFile', () => {
it('works for param types', () => {
const file = program.addOrReplaceFile('source/main.brs', `
sub DoSomething(name as string)
name = 1
sayMyName = function(name as string)
end function
end sub
`);

//hover over the `name = 1` line
let hover = program.getHover(file.pathAbsolute, util.createPosition(2, 24));
expect(hover).to.exist;
expect(hover.range).to.eql(util.createRange(2, 20, 2, 24));

//hover over the `name` parameter declaration
hover = program.getHover(file.pathAbsolute, util.createPosition(1, 34));
expect(hover).to.exist;
expect(hover.range).to.eql(util.createRange(1, 32, 1, 36));
});

//ignore this for now...it's not a huge deal
it('does not match on keywords or data types', () => {
let file = program.addOrReplaceFile({ src: `${rootDir}/source/main.brs`, dest: 'source/main.brs' }, `
sub Main(name as string)
end sub
sub as()
end sub
`);
//hover over the `as`
expect(program.getHover(file.pathAbsolute, util.createPosition(1, 31))).not.to.exist;
//hover over the `string`
expect(program.getHover(file.pathAbsolute, util.createPosition(1, 36))).not.to.exist;
});

it('finds declared function', () => {
let file = program.addOrReplaceFile({ src: `${rootDir}/source/main.brs`, dest: 'source/main.brs' }, `
function Main(count = 1)
firstName = "bob"
age = 21
shoeSize = 10
end function
`);

let hover = program.getHover(file.pathAbsolute, util.createPosition(1, 28));
expect(hover).to.exist;

expect(hover.range).to.eql(util.createRange(1, 25, 1, 29));
expect(hover.contents).to.eql({
language: 'brighterscript',
value: 'function Main(count? as dynamic) as dynamic'
});
});

it('finds variable function hover in same scope', () => {
let file = program.addOrReplaceFile({ src: `${rootDir}/source/main.brs`, dest: 'source/main.brs' }, `
sub Main()
sayMyName = sub(name as string)
end sub

sayMyName()
end sub
`);

let hover = program.getHover(file.pathAbsolute, util.createPosition(5, 24));

expect(hover.range).to.eql(util.createRange(5, 20, 5, 29));
expect(hover.contents).to.eql({
language: 'brighterscript',
value: 'sub sayMyName(name as string) as void'
});
});

it('finds function hover in file scope', () => {
let file = program.addOrReplaceFile({ src: `${rootDir}/source/main.brs`, dest: 'source/main.brs' }, `
sub Main()
sayMyName()
end sub

sub sayMyName()

end sub
`);

let hover = program.getHover(file.pathAbsolute, util.createPosition(2, 25));

expect(hover.range).to.eql(util.createRange(2, 20, 2, 29));
expect(hover.contents).to.eql({
language: 'brighterscript',
value: 'sub sayMyName() as void'
});
});

it('finds function hover in scope', () => {
let rootDir = process.cwd();
program = new Program({
rootDir: rootDir
});

let mainFile = program.addOrReplaceFile({ src: `${rootDir}/source/main.brs`, dest: 'source/main.brs' }, `
sub Main()
sayMyName()
end sub
`);

program.addOrReplaceFile({ src: `${rootDir}/source/lib.brs`, dest: 'source/lib.brs' }, `
sub sayMyName(name as string)

end sub
`);

let hover = program.getHover(mainFile.pathAbsolute, util.createPosition(2, 25));
expect(hover).to.exist;

expect(hover.range).to.eql(util.createRange(2, 20, 2, 29));
expect(hover.contents).to.eql({
language: 'brighterscript',
value: 'sub sayMyName(name as string) as void'
});
});
});
});
Loading