Skip to content
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
45 changes: 43 additions & 2 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,20 @@ While there are no restrictions on plugin names, it helps others to find your pl
Full compiler lifecycle:

- `beforeProgramCreate`
- `onProgramCreate`
- `afterProgramCreate`
- `beforeScopeCreate` ("source" scope)
- `onScopeCreate` ("source" scope)
- `afterScopeCreate` ("source" scope)
- For each file:
- `beforeFileParse`
- `onFileParse`
- `afterFileParse`
- `beforeScopeCreate` (component scope)
- `onScopeCreate` (component scope)
- `afterScopeCreate` (component scope)
- `beforeProgramValidate`
- `onProgramValidate`
- For each file:
- `beforeFileValidate`
- `onFileValidate`
Expand All @@ -68,15 +75,21 @@ Full compiler lifecycle:
- `afterScopeValidate`
- `afterProgramValidate`
- `beforePrepublish`
- `onPrepublish`
- `afterPrepublish`
- `beforePublish`
- `onPublish`
- `beforeProgramTranspile`
- `onProgramTranspile`
- For each file:
- `beforeFileTranspile`
- `onFileTranspile`
- `afterFileTranspile`
- `afterProgramTranspile`
- `afterPublish`
- `beforeProgramDispose`
- `onProgramDispose`
- `afterProgramDispose`

### Language server

Expand All @@ -86,13 +99,18 @@ When a file is removed:

- `beforeFileDispose`
- `beforeScopeDispose` (component scope)
- `onScopeDispose` (component scope)
- `afterScopeDispose` (component scope)
- `onFileDispose`
- `afterFileDispose`

When a file is added:

- `beforeFileParse`
- `onFileParse`
- `afterFileParse`
- `beforeScopeCreate` (component scope)
- `onScopeCreate` (component scope)
- `afterScopeCreate` (component scope)
- `afterFileValidate`

Expand All @@ -104,17 +122,21 @@ When any file is modified:
After file addition/removal (note: throttled/debounced):

- `beforeProgramValidate`
- `onProgramValidate`
- For each invalidated/not-yet-validated file
- `beforeFileValidate`
- `onFileValidate`
- `afterFileValidate`
- For each invalidated scope:
- `beforeScopeValidate`
- `onScopeValidate`
- `afterScopeValidate`
- `afterProgramValidate`

Code Actions
- `beforeGetCodeActions`
- `onGetCodeActions`
- `afterGetCodeActions`

Completions
- `beforeProvideCompletions`
Expand All @@ -127,7 +149,9 @@ Hovers
- `afterProvideHover`

Semantic Tokens
- `onGetSemanticTokens`
- `beforeGetSemanticTokens`
- `onGetSemanticTokens`
- `afterGetSemanticTokens`


## Compiler API
Expand Down Expand Up @@ -157,17 +181,26 @@ export interface CompilerPlugin {
name: string;
//program events
beforeProgramCreate?: (builder: ProgramBuilder) => void;
onProgramCreate?: (program: Program) => void;
afterProgramCreate?: (program: Program) => void;
beforePrepublish?: (builder: ProgramBuilder, files: FileObj[]) => void;
onPrepublish?: (builder: ProgramBuilder, files: FileObj[]) => void;
afterPrepublish?: (builder: ProgramBuilder, files: FileObj[]) => void;
beforePublish?: (builder: ProgramBuilder, files: FileObj[]) => void;
onPublish?: (builder: ProgramBuilder, files: FileObj[]) => void;
afterPublish?: (builder: ProgramBuilder, files: FileObj[]) => void;
afterProgramCreate?: (program: Program) => void;
beforeProgramValidate?: (program: Program) => void;
onProgramValidate?: (program: Program) => void;
afterProgramValidate?: (program: Program) => void;
beforeProgramTranspile?: (program: Program, entries: TranspileObj[], editor: AstEditor) => void;
onProgramTranspile?: (program: Program, entries: TranspileObj[], editor: AstEditor) => void;
afterProgramTranspile?: (program: Program, entries: TranspileObj[], editor: AstEditor) => void;
beforeProgramDispose?: PluginHandler<BeforeProgramDisposeEvent>;
onProgramDispose?: PluginHandler<OnProgramDisposeEvent>;
afterProgramDispose?: PluginHandler<AfterProgramDisposeEvent>;
beforeGetCodeActions?: PluginHandler<BeforeGetCodeActionsEvent>;
onGetCodeActions?: PluginHandler<OnGetCodeActionsEvent>;
afterGetCodeActions?: PluginHandler<AfterGetCodeActionsEvent>;

/**
* Emitted before the program starts collecting completions
Expand Down Expand Up @@ -259,16 +292,22 @@ export interface CompilerPlugin {
afterProvideWorkspaceSymbols?(event: AfterProvideWorkspaceSymbolsEvent): any;


beforeGetSemanticTokens?: PluginHandler<BeforeGetSemanticTokensEvent>;
onGetSemanticTokens?: PluginHandler<OnGetSemanticTokensEvent>;
afterGetSemanticTokens?: PluginHandler<AfterGetSemanticTokensEvent>;
//scope events
beforeScopeCreate?: (scope: Scope) => void;
onScopeCreate?: (scope: Scope) => void;
afterScopeCreate?: (scope: Scope) => void;
beforeScopeDispose?: (scope: Scope) => void;
onScopeDispose?: (scope: Scope) => void;
afterScopeDispose?: (scope: Scope) => void;
beforeScopeValidate?: ValidateHandler;
onScopeValidate?: PluginHandler<OnScopeValidateEvent>;
afterScopeValidate?: ValidateHandler;
//file events
beforeFileParse?: (source: SourceObj) => void;
onFileParse?: (source: SourceObj) => void;
afterFileParse?: (file: BscFile) => void;
/**
* Called before each file is validated
Expand All @@ -283,8 +322,10 @@ export interface CompilerPlugin {
*/
afterFileValidate?: (file: BscFile) => void;
beforeFileTranspile?: PluginHandler<BeforeFileTranspileEvent>;
onFileTranspile?: PluginHandler<OnFileTranspileEvent>;
afterFileTranspile?: PluginHandler<AfterFileTranspileEvent>;
beforeFileDispose?: (file: BscFile) => void;
onFileDispose?: (file: BscFile) => void;
afterFileDispose?: (file: BscFile) => void;
}

Expand Down
107 changes: 105 additions & 2 deletions src/Program.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,21 +185,29 @@ describe('Program', () => {

it(`emits events for scope and file creation`, () => {
const beforeProgramValidate = sinon.spy();
const onProgramValidate = sinon.spy();
const afterProgramValidate = sinon.spy();
const beforeScopeCreate = sinon.spy();
const onScopeCreate = sinon.spy();
const afterScopeCreate = sinon.spy();
const beforeScopeValidate = sinon.spy();
const afterScopeValidate = sinon.spy();
const beforeFileParse = sinon.spy();
const onFileParse = sinon.spy();
const afterFileParse = sinon.spy();
const afterFileValidate = sinon.spy();
program.plugins = new PluginInterface([{
name: 'emits events for scope and file creation',
beforeProgramValidate: beforeProgramValidate,
onProgramValidate: onProgramValidate,
afterProgramValidate: afterProgramValidate,
beforeScopeCreate: beforeScopeCreate,
onScopeCreate: onScopeCreate,
afterScopeCreate: afterScopeCreate,
beforeScopeValidate: beforeScopeValidate,
afterScopeValidate: afterScopeValidate,
beforeFileParse: beforeFileParse,
onFileParse: onFileParse,
afterFileParse: afterFileParse,
afterFileValidate: afterFileValidate
}], { logger: createLogger() });
Expand All @@ -216,14 +224,18 @@ describe('Program', () => {

//program events
expect(beforeProgramValidate.callCount).to.equal(1);
expect(onProgramValidate.callCount).to.equal(1);
expect(afterProgramValidate.callCount).to.equal(1);
//scope events
//(we get component scope event only because source is created in beforeEach)
expect(beforeScopeCreate.callCount).to.equal(1);
expect(onScopeCreate.callCount).to.equal(1);
expect(afterScopeCreate.callCount).to.equal(1);
expect(beforeScopeValidate.callCount).to.equal(2);
expect(afterScopeValidate.callCount).to.equal(2);
//file events
expect(beforeFileParse.callCount).to.equal(2);
expect(onFileParse.callCount).to.equal(2);
expect(afterFileParse.callCount).to.equal(2);
expect(afterFileValidate.callCount).to.equal(2);
});
Expand Down Expand Up @@ -2439,12 +2451,18 @@ describe('Program', () => {
print "hello world"
end sub
`);
const onFileTranspile = sinon.spy();
const onProgramTranspile = sinon.spy();
const afterProgramTranspile = sinon.spy();
const plugin = program.plugins.add({
name: 'TestPlugin',
beforeFileTranspile: (event) => {
const stmt = ((event.file as BrsFile).ast.statements[0] as FunctionStatement).func.body.statements[0] as PrintStatement;
event.editor.setProperty((stmt.expressions[0] as LiteralExpression).token, 'text', '"hello there"');
},
onFileTranspile: onFileTranspile,
onProgramTranspile: onProgramTranspile,
afterProgramTranspile: afterProgramTranspile,
afterFileTranspile: sinon.spy()
});
expect(
Expand All @@ -2454,6 +2472,9 @@ describe('Program', () => {
print "hello there"
end sub`
);
expect(onFileTranspile.callCount).to.be.greaterThan(0);
expect(onProgramTranspile.callCount).to.be.greaterThan(0);
expect(afterProgramTranspile.callCount).to.be.greaterThan(0);
expect(plugin.afterFileTranspile.callCount).to.be.greaterThan(0);
});

Expand Down Expand Up @@ -3673,14 +3694,96 @@ describe('Program', () => {
expect(plugin.afterFileValidate.callCount).to.equal(1);
});

it('emits program dispose event', () => {
it('emits xml file and component scope dispose events', () => {
const plugin = {
name: 'test',
beforeProgramDispose: sinon.spy()
beforeFileDispose: sinon.spy(),
onFileDispose: sinon.spy(),
afterFileDispose: sinon.spy(),
beforeScopeDispose: sinon.spy(),
onScopeDispose: sinon.spy(),
afterScopeDispose: sinon.spy()
};
program.plugins.add(plugin);
program.setFile('components/main.xml', `
<component name="main" extends="Group" />
`);
program.removeFile('components/main.xml');
expect(plugin.beforeFileDispose.callCount).to.equal(1);
expect(plugin.onFileDispose.callCount).to.equal(1);
expect(plugin.afterFileDispose.callCount).to.equal(1);
expect(plugin.beforeScopeDispose.callCount).to.equal(1);
expect(plugin.onScopeDispose.callCount).to.equal(1);
expect(plugin.afterScopeDispose.callCount).to.equal(1);
});

it('emits brs file dispose events', () => {
const plugin = {
name: 'test',
beforeFileDispose: sinon.spy(),
onFileDispose: sinon.spy(),
afterFileDispose: sinon.spy()
};
program.plugins.add(plugin);
program.setFile('source/main.brs', `
sub main()
end sub
`);
program.removeFile('source/main.brs');
expect(plugin.beforeFileDispose.callCount).to.equal(1);
expect(plugin.onFileDispose.callCount).to.equal(1);
expect(plugin.afterFileDispose.callCount).to.equal(1);
});

it('emits program dispose events', () => {
const plugin = {
name: 'test',
beforeProgramDispose: sinon.spy(),
onProgramDispose: sinon.spy(),
afterProgramDispose: sinon.spy()
};
program.plugins.add(plugin);
program.dispose();
expect(plugin.beforeProgramDispose.callCount).to.equal(1);
expect(plugin.onProgramDispose.callCount).to.equal(1);
expect(plugin.afterProgramDispose.callCount).to.equal(1);
});

it('emits before/on/after code-action-like events', () => {
const plugin = {
name: 'test',
beforeGetCodeActions: sinon.spy(),
onGetCodeActions: sinon.spy(),
afterGetCodeActions: sinon.spy(),
beforeGetSourceFixAllCodeActions: sinon.spy(),
onGetSourceFixAllCodeActions: sinon.spy(),
afterGetSourceFixAllCodeActions: sinon.spy(),
beforeGetSemanticTokens: sinon.spy(),
onGetSemanticTokens: sinon.spy(),
afterGetSemanticTokens: sinon.spy()
};
program.plugins.add(plugin);
program.setFile('source/main.bs', `
sub main()
end sub
`);
program.validate();
const srcPath = s`${rootDir}/source/main.bs`;
program.getCodeActions(srcPath, Range.create(0, 0, 0, 0));
program.getSourceFixAllCodeActions(srcPath);
program.getSemanticTokens(srcPath);

expect(plugin.beforeGetCodeActions.callCount).to.equal(1);
expect(plugin.onGetCodeActions.callCount).to.equal(1);
expect(plugin.afterGetCodeActions.callCount).to.equal(1);

expect(plugin.beforeGetSourceFixAllCodeActions.callCount).to.equal(1);
expect(plugin.onGetSourceFixAllCodeActions.callCount).to.equal(1);
expect(plugin.afterGetSourceFixAllCodeActions.callCount).to.equal(1);

expect(plugin.beforeGetSemanticTokens.callCount).to.equal(1);
expect(plugin.onGetSemanticTokens.callCount).to.equal(1);
expect(plugin.afterGetSemanticTokens.callCount).to.equal(1);
});
});

Expand Down
Loading