Skip to content

Commit

Permalink
Add support for provideReferences in plugins (#1066)
Browse files Browse the repository at this point in the history
* Add support for `provideReferences` in plugins

* Add plugin docs updates
  • Loading branch information
TwitchBronBron committed Feb 9, 2024
1 parent 074877a commit ee1d7f9
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 34 deletions.
32 changes: 32 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,38 @@ export interface CompilerPlugin {
*/
afterProvideHover?: PluginHandler<AfterProvideHoverEvent>;

/**
* Called before the `provideDefinition` hook
*/
beforeProvideDefinition?(event: BeforeProvideDefinitionEvent): any;
/**
* Provide one or more `Location`s where the symbol at the given position was originally defined
* @param event
*/
provideDefinition?(event: ProvideDefinitionEvent): any;
/**
* Called after `provideDefinition`. Use this if you want to intercept or sanitize the definition data provided by bsc or other plugins
* @param event
*/
afterProvideDefinition?(event: AfterProvideDefinitionEvent): any;


/**
* Called before the `provideReferences` hook
*/
beforeProvideReferences?(event: BeforeProvideReferencesEvent): any;
/**
* Provide all of the `Location`s where the symbol at the given position is located
* @param event
*/
provideReferences?(event: ProvideReferencesEvent): any;
/**
* Called after `provideReferences`. Use this if you want to intercept or sanitize the references data provided by bsc or other plugins
* @param event
*/
afterProvideReferences?(event: AfterProvideReferencesEvent): any;


onGetSemanticTokens?: PluginHandler<OnGetSemanticTokensEvent>;
//scope events
afterScopeCreate?: (scope: Scope) => void;
Expand Down
17 changes: 14 additions & 3 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, SemanticToken, AfterFileTranspileEvent, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, ProvideDefinitionEvent } from './interfaces';
import type { BsDiagnostic, File, FileReference, FileObj, BscFile, SemanticToken, AfterFileTranspileEvent, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, ProvideDefinitionEvent, ProvideReferencesEvent } from './interfaces';
import { standardizePath as s, util } from './util';
import { XmlScope } from './XmlScope';
import { DiagnosticFilterer } from './DiagnosticFilterer';
Expand Down Expand Up @@ -1017,14 +1017,25 @@ export class Program {
return signatureHelpUtil.getSignatureHelpItems(callExpressionInfo);
}

public getReferences(srcPath: string, position: Position) {
public getReferences(srcPath: string, position: Position): Location[] {
//find the file
let file = this.getFile(srcPath);
if (!file) {
return null;
}

return file.getReferences(position);
const event: ProvideReferencesEvent = {
program: this,
file: file,
position: position,
references: []
};

this.plugins.emit('beforeProvideReferences', event);
this.plugins.emit('provideReferences', event);
this.plugins.emit('afterProvideReferences', event);

return event.references;
}

/**
Expand Down
7 changes: 6 additions & 1 deletion src/bscPlugin/BscPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { isBrsFile, isXmlFile } from '../astUtils/reflection';
import type { BeforeFileTranspileEvent, CompilerPlugin, OnFileValidateEvent, OnGetCodeActionsEvent, ProvideHoverEvent, OnGetSemanticTokensEvent, OnScopeValidateEvent, ProvideCompletionsEvent, ProvideDefinitionEvent } from '../interfaces';
import type { BeforeFileTranspileEvent, CompilerPlugin, OnFileValidateEvent, OnGetCodeActionsEvent, ProvideHoverEvent, OnGetSemanticTokensEvent, OnScopeValidateEvent, ProvideCompletionsEvent, ProvideDefinitionEvent, ProvideReferencesEvent } from '../interfaces';
import type { Program } from '../Program';
import { CodeActionsProcessor } from './codeActions/CodeActionsProcessor';
import { CompletionsProcessor } from './completions/CompletionsProcessor';
import { DefinitionProvider } from './definition/DefinitionProvider';
import { HoverProcessor } from './hover/HoverProcessor';
import { ReferencesProvider } from './references/ReferencesProvider';
import { BrsFileSemanticTokensProcessor } from './semanticTokens/BrsFileSemanticTokensProcessor';
import { BrsFilePreTranspileProcessor } from './transpile/BrsFilePreTranspileProcessor';
import { BrsFileValidator } from './validation/BrsFileValidator';
Expand All @@ -31,6 +32,10 @@ export class BscPlugin implements CompilerPlugin {
new DefinitionProvider(event).process();
}

public provideReferences(event: ProvideReferencesEvent) {
new ReferencesProvider(event).process();
}

public onGetSemanticTokens(event: OnGetSemanticTokensEvent) {
if (isBrsFile(event.file)) {
return new BrsFileSemanticTokensProcessor(event as any).process();
Expand Down
58 changes: 58 additions & 0 deletions src/bscPlugin/references/ReferencesProvider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { expect } from '../../chai-config.spec';
import { Program } from '../../Program';
import { standardizePath as s, util } from '../../util';
let rootDir = s`${process.cwd()}/rootDir`;
import { createSandbox } from 'sinon';
import { ReferencesProvider } from './ReferencesProvider';
import type { Location } from 'vscode-languageserver-protocol';
import { URI } from 'vscode-uri';
const sinon = createSandbox();

describe('ReferencesProvider', () => {
let program: Program;
beforeEach(() => {
program = new Program({
rootDir: rootDir
});
sinon.restore();
});

afterEach(() => {
program.dispose();
sinon.restore();
});

it('handles unknown file type', () => {
const result = new ReferencesProvider({
program: program,
file: undefined,
position: util.createPosition(1, 2),
references: []
}).process();
expect(result).to.eql([]);
});

it('finds references for variables in same function', () => {
const file = program.setFile('source/main.brs', `
sub main()
name = "John"
print name
name = name + " Doe"
end sub
`);
expect(
util.sortByRange(
program.getReferences('source/main.brs', util.createPosition(3, 25))
).map(locationToString)
).to.eql([
s`${file.srcPath}:2:16-2:20`,
s`${file.srcPath}:3:22-3:26`,
s`${file.srcPath}:4:16-4:20`,
s`${file.srcPath}:4:23-4:27`
]);
});

function locationToString(loc: Location) {
return `${URI.parse(loc.uri).fsPath}:${loc.range.start.line}:${loc.range.start.character}-${loc.range.end.line}:${loc.range.end.character}`;
}
});
62 changes: 62 additions & 0 deletions src/bscPlugin/references/ReferencesProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { BrsFile } from '../../files/BrsFile';
import type { ProvideReferencesEvent } from '../../interfaces';
import type { Location } from 'vscode-languageserver-protocol';
import util from '../../util';
import { WalkMode, createVisitor } from '../../astUtils/visitors';
import type { XmlFile } from '../../files/XmlFile';
import { isBrsFile, isXmlFile } from '../../astUtils/reflection';

export class ReferencesProvider {
constructor(
private event: ProvideReferencesEvent
) { }

public process(): Location[] {
if (isBrsFile(this.event.file)) {
this.brsFileGetReferences(this.event.file);
} else if (isXmlFile(this.event.file)) {
this.xmlFileGetReferences(this.event.file);
}
return this.event.references;
}

/**
* For a position in a BrsFile, get the location where the token at that position was defined
*/
private brsFileGetReferences(file: BrsFile): void {

const callSiteToken = file.getTokenAt(this.event.position);

const searchFor = callSiteToken.text.toLowerCase();

const scopes = this.event.program.getScopesForFile(file);

for (const scope of scopes) {
const processedFiles = new Set<BrsFile>();
for (const file of scope.getAllFiles()) {
if (!isBrsFile(file) || processedFiles.has(file)) {
continue;
}
processedFiles.add(file);
file.ast.walk(createVisitor({
AssignmentStatement: (s) => {
if (s.name?.text?.toLowerCase() === searchFor) {
this.event.references.push(util.createLocation(util.pathToUri(file.srcPath), s.name.range));
}
},
VariableExpression: (e) => {
if (e.name.text.toLowerCase() === searchFor) {
this.event.references.push(util.createLocation(util.pathToUri(file.srcPath), e.range));
}
}
}), {
walkMode: WalkMode.visitAllRecursive
});
}
}
}

private xmlFileGetReferences(file: XmlFile) {

}
}
44 changes: 14 additions & 30 deletions src/files/BrsFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ import { BrsTranspileState } from '../parser/BrsTranspileState';
import { Preprocessor } from '../preprocessor/Preprocessor';
import { LogLevel } from '../Logger';
import { serializeError } from 'serialize-error';
import { isCallExpression, isMethodStatement, isClassStatement, isDottedGetExpression, isFunctionExpression, isFunctionStatement, isFunctionType, isLiteralExpression, isNamespaceStatement, isStringType, isVariableExpression, isXmlFile, isImportStatement, isFieldStatement, isEnumStatement, isConstStatement } from '../astUtils/reflection';
import { isCallExpression, isMethodStatement, isClassStatement, isDottedGetExpression, isFunctionExpression, isFunctionStatement, isFunctionType, isLiteralExpression, isNamespaceStatement, isStringType, isVariableExpression, isImportStatement, isFieldStatement, isEnumStatement, isConstStatement } from '../astUtils/reflection';
import type { BscType } from '../types/BscType';
import { createVisitor, WalkMode } from '../astUtils/visitors';
import type { DependencyGraph } from '../DependencyGraph';
import { CommentFlagProcessor } from '../CommentFlagProcessor';
import type { AstNode, Expression, Statement } from '../parser/AstNode';
import { DefinitionProvider } from '../bscPlugin/definition/DefinitionProvider';
import { ReferencesProvider } from '../bscPlugin/references/ReferencesProvider';

/**
* Holds all details about this file within the scope of the whole program
Expand Down Expand Up @@ -1454,35 +1455,18 @@ export class BrsFile {
return statement;
}

public getReferences(position: Position) {

const callSiteToken = this.getTokenAt(position);

let locations = [] as Location[];

const searchFor = callSiteToken.text.toLowerCase();

const scopes = this.program.getScopesForFile(this);

for (const scope of scopes) {
const processedFiles = new Set<BrsFile>();
for (const file of scope.getAllFiles()) {
if (isXmlFile(file) || processedFiles.has(file)) {
continue;
}
processedFiles.add(file);
file.ast.walk(createVisitor({
VariableExpression: (e) => {
if (e.name.text.toLowerCase() === searchFor) {
locations.push(util.createLocation(util.pathToUri(file.srcPath), e.range));
}
}
}), {
walkMode: WalkMode.visitExpressionsRecursive
});
}
}
return locations;
/**
* Given a position in a file, if the position is sitting on some type of identifier,
* look up all references of that identifier (every place that identifier is used across the whole app)
* @deprecated use `ReferencesProvider.process()` instead
*/
public getReferences(position: Position): Location[] {
return new ReferencesProvider({
program: this.program,
file: this,
position: position,
references: []
}).process();
}

/**
Expand Down
35 changes: 35 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,23 @@ export interface CompilerPlugin {
*/
afterProvideDefinition?(event: AfterProvideDefinitionEvent): any;


/**
* Called before the `provideReferences` hook
*/
beforeProvideReferences?(event: BeforeProvideReferencesEvent): any;
/**
* Provide all of the `Location`s where the symbol at the given position is located
* @param event
*/
provideReferences?(event: ProvideReferencesEvent): any;
/**
* Called after `provideReferences`. Use this if you want to intercept or sanitize the references data provided by bsc or other plugins
* @param event
*/
afterProvideReferences?(event: AfterProvideReferencesEvent): any;


onGetSemanticTokens?: PluginHandler<OnGetSemanticTokensEvent>;
//scope events
afterScopeCreate?: (scope: Scope) => void;
Expand Down Expand Up @@ -336,6 +353,24 @@ export interface ProvideDefinitionEvent<TFile = BscFile> {
export type BeforeProvideDefinitionEvent<TFile = BscFile> = ProvideDefinitionEvent<TFile>;
export type AfterProvideDefinitionEvent<TFile = BscFile> = ProvideDefinitionEvent<TFile>;

export interface ProvideReferencesEvent<TFile = BscFile> {
program: Program;
/**
* The file that the getDefinition request was invoked in
*/
file: TFile;
/**
* The position in the text document where the getDefinition request was invoked
*/
position: Position;
/**
* The list of locations for where the item at the file and position was defined
*/
references: Location[];
}
export type BeforeProvideReferencesEvent<TFile = BscFile> = ProvideReferencesEvent<TFile>;
export type AfterProvideReferencesEvent<TFile = BscFile> = ProvideReferencesEvent<TFile>;


export interface OnGetSemanticTokensEvent<T extends BscFile = BscFile> {
/**
Expand Down

0 comments on commit ee1d7f9

Please sign in to comment.