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

Add support for provideReferences in plugins #1066

Merged
merged 2 commits into from
Feb 9, 2024
Merged
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: 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