diff --git a/docs/plugins.md b/docs/plugins.md index 196055d49..4f7ae06c7 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -50,39 +50,37 @@ While there are no restrictions on plugin names, it helps others to find your pl Full compiler lifecycle: -- `beforeProgramCreate` -- `afterProgramCreate` - - `afterScopeCreate` ("source" scope) +- `beforeProvideProgram` +- `afterProvideProgram` + - `afterProvideScope` ("source" scope) - For each physical file: - `beforeProvideFile` - `onProvideFile` - `afterProvideFile` - - `beforeFileParse` (deprecated) - - `afterFileParse` (deprecated) - For each physical and virtual file - `beforeAddFile` - `afterAddFile` - - `afterScopeCreate` (component scope) - - `beforeProgramValidate` + - `afterProvideScope` (component scope) + - `beforeValidateProgram` - For each file: - - `beforeFileValidate` - - `onFileValidate` - - `afterFileValidate` + - `beforeValidateFile` + - `validateFile` + - `afterValidateFile` - For each scope: - - `beforeScopeValidate` - - `onScopeValidate` - - `afterScopeValidate` - - `afterProgramValidate` + - `beforeValidateScope` + - `validateScope` + - `afterValidateScope` + - `afterValidateProgram` - `beforePrepublish` - `afterPrepublish` -- `beforePublish` - - `beforeProgramTranspile` +- `beforeSerializeProgram` + - `beforeBuildProgram` - For each file: - - `beforeFileTranspile` - - `afterFileTranspile` - - `afterProgramTranspile` -- `afterPublish` -- `beforeProgramDispose` + - `beforePrepareFile` + - `afterPrepareFile` + - `afterBuildProgram` +- `afterSerializeProgram` +- `beforeRemoveProgram` ### Language server @@ -90,17 +88,17 @@ Once the program has been validated, the language server runs a special loop - i When a file is removed: -- `beforeFileDispose` -- `beforeScopeDispose` (component scope) -- `afterScopeDispose` (component scope) -- `afterFileDispose` +- `beforeRemoveFile` +- `beforeRemoveScope` (component scope) +- `afterRemoveScope` (component scope) +- `afterRemoveFile` When a file is added: -- `beforeFileParse` -- `afterFileParse` -- `afterScopeCreate` (component scope) -- `afterFileValidate` +- `beforeProvideFile` +- `afterProvideFile` +- `afterProvideScope` (component scope) +- `afterValidateFile` When any file is modified: @@ -109,18 +107,18 @@ When any file is modified: After file addition/removal (note: throttled/debounced): -- `beforeProgramValidate` +- `beforeValidateProgram` - For each invalidated/not-yet-validated file - - `beforeFileValidate` - - `onFileValidate` - - `afterFileValidate` + - `beforeValidateFile` + - `validateFile` + - `afterValidateFile` - For each invalidated scope: - - `beforeScopeValidate` - - `afterScopeValidate` -- `afterProgramValidate` + - `beforeValidateScope` + - `afterValidateScope` +- `afterValidateProgram` Code Actions - - `onGetCodeActions` + - `provideCodeActions` Completions - `beforeProvideCompletions` @@ -133,7 +131,7 @@ Hovers - `afterProvideHover` Semantic Tokens - - `onGetSemanticTokens` + - `provideSemanticTokens` ## Compiler API @@ -164,11 +162,11 @@ export interface CompilerPlugin { /** * Called before a new program is created */ - beforeProgramCreate?(event: BeforeProgramCreateEvent): any; + beforeProvideProgram?(event: BeforeProvideProgramEvent): any; /** * Called after a new program is created */ - afterProgramCreate?(event: AfterProgramCreateEvent): any; + afterProvideProgram?(event: AfterProvideProgramEvent): any; /** @@ -188,20 +186,20 @@ export interface CompilerPlugin { /** * Called before the entire program is validated */ - beforeProgramValidate?(event: BeforeProgramValidateEvent): any; + beforeValidateProgram?(event: BeforeValidateProgramEvent): any; /** * Called before the entire program is validated */ - onProgramValidate?(event: OnProgramValidateEvent): any; + validateProgram?(event: ValidateProgramEvent): any; /** * Called after the program has been validated */ - afterProgramValidate?(event: AfterProgramValidateEvent): any; + afterValidateProgram?(event: AfterValidateProgramEvent): any; /** * Called right before the program is disposed/destroyed */ - beforeProgramDispose?(event: BeforeProgramDisposeEvent): any; + beforeRemoveProgram?(event: BeforeRemoveProgramEvent): any; /** * Emitted before the program starts collecting completions @@ -233,13 +231,13 @@ export interface CompilerPlugin { /** * Called after a scope was created */ - afterScopeCreate?(event: AfterScopeCreateEvent): any; + afterProvideScope?(event: AfterProvideScopeEvent): any; - beforeScopeDispose?(event: BeforeScopeDisposeEvent): any; - onScopeDispose?(event: OnScopeDisposeEvent): any; - afterScopeDispose?(event: AfterScopeDisposeEvent): any; + beforeRemoveScope?(event: BeforeRemoveProgramEvent): any; + removeScope?(event: RemoveScopeEvent): any; + afterRemoveScope?(event: AfterRemoveScopeEvent): any; - beforeScopeValidate?(event: BeforeScopeValidateEvent): any; + beforeValidateScope?(event: BeforeValidateScopeEvent): any; /** * Called before the `provideDefinition` hook @@ -305,13 +303,13 @@ export interface CompilerPlugin { afterProvideWorkspaceSymbols?(event: AfterProvideWorkspaceSymbolsEvent): any; - onGetSemanticTokens?: PluginHandler; + provideSemanticTokens?: PluginHandler; //scope events - onScopeValidate?(event: OnScopeValidateEvent): any; - afterScopeValidate?(event: BeforeScopeValidateEvent): any; + validateScope?(event: ValidateScopeEvent): any; + afterValidateScope?(event: BeforeValidateScopeEvent): any; - onGetCodeActions?(event: OnGetCodeActionsEvent): any; - onGetSemanticTokens?(event: OnGetSemanticTokensEvent): any; + provideCodeActions?(event: ProvideCodeActionsEvent): any; + provideSemanticTokens?(event: ProvideSemanticTokensEvent): any; /** @@ -333,35 +331,35 @@ export interface CompilerPlugin { * Called before a file is added to the program. * Includes physical files as well as any virtual files produced by `provideFile` events */ - beforeFileAdd?(event: BeforeFileAddEvent): any; + beforeAddFile?(event: BeforeAddFileEvent): any; /** * Called after a file has been added to the program. * Includes physical files as well as any virtual files produced by `provideFile` events */ - afterFileAdd?(event: AfterFileAddEvent): any; + afterAddFile?(event: AfterAddFileEvent): any; /** * Called before a file is removed from the program. This includes physical and virtual files */ - beforeFileRemove?(event: BeforeFileRemoveEvent): any; + beforeRemoveFile?(event: BeforeRemoveFileEvent): any; /** * Called after a file has been removed from the program. This includes physical and virtual files */ - afterFileRemove?(event: AfterFileRemoveEvent): any; + afterRemoveFile?(event: AfterRemoveFileEvent): any; /** * Called before each file is validated */ - beforeFileValidate?(event: BeforeFileValidateEvent): any; + beforeValidateFile?(event: BeforeValidateFileEvent): any; /** * Called during the file validation process. If your plugin contributes file validations, this is a good place to contribute them. */ - onFileValidate?(event: OnFileValidateEvent): any; + validateFile?(event: ValidateFileEvent): any; /** * Called after each file is validated */ - afterFileValidate?(event: AfterFileValidateEvent): any; + afterValidateFile?(event: AfterValidateFileEvent): any; /** @@ -395,7 +393,7 @@ export interface CompilerPlugin { /** * Emitted right at the start of the program turning all file objects into their final buffers */ - onSerializeProgram?(event: OnSerializeProgramEvent): any; + serializeProgram?(event: SerializeProgramEvent): any; /** * After the program turns all file objects into their final buffers */ @@ -542,8 +540,8 @@ npx tsc myPlugin.ts -m commonjs --sourceMap --watch Diagnostics must run every time it is relevant: -- Files have one out-of-context validation step (`afterFileValidate`), -- Scopes are validated once all the the files have been collected (`before/afterScopeValidate`). +- Files have one out-of-context validation step (`afterValidateFile`), +- Scopes are validated once all the the files have been collected (`before/afterValidateScope`). Note: in a language-server context, Scope validation happens every time a file changes. @@ -558,7 +556,7 @@ export default function () { return { name: 'no-underscores', // post-parsing validation - afterFileValidate: (event: AfterFileValidateEvent) => { + afterValidateFile: (event: AfterValidateFileEvent) => { if (isBrsFile(event.file)) { // clear existing diagnostics event.program.diagnosticManager.clearByContext({file: file, tag: 'no-underscores-plugin'}); @@ -587,7 +585,7 @@ export default function () { ## Modifying code Sometimes plugins will want to modify code before the project is transpiled. While you can technically edit the AST directly at any point in the file's lifecycle, this is not recommended as those changes will remain changed as long as that file exists in memory and could cause issues with file validation if the plugin is used in a language-server context (i.e. inside vscode). -Instead, we provide an instace of an `Editor` class in the `beforeFileTranspile` event that allows you to modify AST before the file is transpiled, and then those modifications are undone `afterFileTranspile`. +Instead, we provide an instace of an `Editor` class in the `beforePrepareFile` event that allows you to modify AST before the file is transpiled, and then those modifications are undone `afterPrepareFile`. For example, consider the following brightscript code: ```brightscript @@ -606,7 +604,7 @@ export default function () { return { name: 'replacePlaceholders', // transform AST before transpilation - beforeFileTranspile: (event: BeforeFileTranspileEvent) => { + beforePrepareFile: (event: BeforeFileTranspileEvent) => { if (isBrsFile(event.file)) { event.file.ast.walk(createVisitor({ LiteralExpression: (literal) => { @@ -670,14 +668,14 @@ export default function (): Plugin { ## Modifying `bsconfig.json` via a plugin -In some cases you may want to modify the project's configuration via a plugin, such as to change settings based on environment variables or to dynamically modify the project's `files` array. Plugins may do so in the `beforeProgramCreate` step. For example, here's a plugin which adds an additional file to the build: +In some cases you may want to modify the project's configuration via a plugin, such as to change settings based on environment variables or to dynamically modify the project's `files` array. Plugins may do so in the `beforeProvideProgram` step. For example, here's a plugin which adds an additional file to the build: ```typescript import { CompilerPlugin, ProgramBuilder } from 'brighterscript'; export default function plugin() { return { name: 'addFilesDynamically', - beforeProgramCreate: (builder: ProgramBuilder) => { + beforeProvideProgram: (builder: ProgramBuilder) => { if (!builder.options.files) { builder.options.files = []; } @@ -791,7 +789,7 @@ You can see examples of this in the previous code snippets above. ### Program changes Historically, only `.brs`, `.bs`, and `.xml` files would be present in the `Program`. As a result of the File API being introduced, now all files included as a result of the bsconfig.json `files` array will be present in the program. Unhandled files will be loaded as generic `AssetFile` instances. This may impact plugins that aren't properly guarding against specific file types. Consider this plugin code: ```typescript -onFileValidate(event){ +validateFile(event){ if (isXmlFile(event.file)) { // do XmlFile work } else { diff --git a/src/LanguageServer.spec.ts b/src/LanguageServer.spec.ts index 144dad07c..5893f78d5 100644 --- a/src/LanguageServer.spec.ts +++ b/src/LanguageServer.spec.ts @@ -1723,7 +1723,7 @@ describe('LanguageServer', () => { expect(result['pathAbsolute']).to.eql(result.srcPath); }); - it('calls beforeProgramTranspile and afterProgramTranspile plugin events', async () => { + it('calls beforeBuildProgram and afterBuildProgram plugin events', async () => { fsExtra.outputFileSync(s`${rootDir}/source/main.bs`, ` sub main() print \`hello world\` @@ -1736,7 +1736,7 @@ describe('LanguageServer', () => { //make a plugin that changes string text (server['projectManager'].projects[0] as Project)['builder'].program.plugins.add({ name: 'test-plugin', - beforeProgramTranspile: (event) => { + beforeBuildProgram: (event) => { const { program, editor } = event; const file = program.getFile('source/main.bs'); if (isBrsFile(file)) { @@ -1751,7 +1751,7 @@ describe('LanguageServer', () => { }); } }, - afterProgramTranspile: afterSpy + afterBuildProgram: afterSpy }); const result = (await server.onExecuteCommand({ diff --git a/src/PluginInterface.ts b/src/PluginInterface.ts index c0919e221..2499736dd 100644 --- a/src/PluginInterface.ts +++ b/src/PluginInterface.ts @@ -142,15 +142,37 @@ export default class PluginInterface { afterFileParse: 'afterProvideFile', beforeFileTranspile: 'beforePrepareFile', afterFileTranspile: 'afterPrepareFile', - beforeFileDispose: 'beforeFileRemove', - afterFileDispose: 'afterFileRemove' + beforeFileDispose: 'beforeRemoveFile', + afterFileDispose: 'afterRemoveFile', + beforeProgramCreate: 'beforeProvideProgram', + afterProgramCreate: 'afterProvideProgram', + beforeProgramValidate: 'beforeValidateProgram', + onProgramValidate: 'validateProgram', + beforeProgramDispose: 'beforeRemoveProgram', + afterScopeCreate: 'afterProvideScope', + beforeScopeDispose: 'beforeRemoveScope', + onScopeDispose: 'removeScope', + afterScopeDispose: 'afterRemoveScope', + beforeScopeValidate: 'beforeValidateScope', + onScopeValidate: 'validateScope', + afterScopeValidate: 'afterValidateScope', + onGetCodeActions: 'provideCodeActions', + onGetSemanticTokens: 'provideSemanticTokens', + beforeFileAdd: 'beforeAddFile', + afterFileAdd: 'afterAddFile', + beforeFileRemove: 'beforeRemoveFile', + afterFileRemove: 'afterRemoveFile', + beforeFileValidate: 'beforeValidateFile', + onFileValidate: 'validateFile', + afterFileValidate: 'afterValidateFile', + onSerializeProgram: 'serializeProgram' }; for (const [oldEvent, newEvent] of Object.entries(upgradeWithWarn)) { if (plugin[oldEvent]) { if (!plugin[newEvent]) { plugin[newEvent] = plugin[oldEvent]; - this.logger?.warn(`Plugin '${plugin.name}': event '${oldEvent}' is no longer supported. It has been converted to '${newEvent}' but you may encounter issues as their signatures do not match.`); + this.logger?.warn(`Plugin '${plugin.name}': event '${oldEvent}' is no longer supported. It has been converted to '${newEvent}' but you may encounter issues as their signatures may not match.`); } else { this.logger?.warn(`Plugin "${plugin.name}": event '${oldEvent}' is no longer supported and will never be called`); } diff --git a/src/Program.spec.ts b/src/Program.spec.ts index f3e3a5fa3..b1c0839bb 100644 --- a/src/Program.spec.ts +++ b/src/Program.spec.ts @@ -19,7 +19,7 @@ import type { SinonSpy } from 'sinon'; import { createSandbox } from 'sinon'; import { SymbolTypeFlag } from './SymbolTypeFlag'; import { AssetFile } from './files/AssetFile'; -import type { ProvideFileEvent, Plugin, BeforeProvideFileEvent, AfterProvideFileEvent, BeforeFileAddEvent, AfterFileAddEvent, BeforeFileRemoveEvent, AfterFileRemoveEvent, ScopeValidationOptions, AfterFileValidateEvent, BeforeScopeValidateEvent, OnScopeValidateEvent, BeforeFileValidateEvent, OnFileValidateEvent, AfterScopeValidateEvent, CompilerPlugin } from './interfaces'; +import type { ProvideFileEvent, Plugin, BeforeProvideFileEvent, AfterProvideFileEvent, BeforeAddFileEvent, AfterAddFileEvent, BeforeRemoveFileEvent, AfterRemoveFileEvent, ScopeValidationOptions, AfterValidateFileEvent, BeforeValidateScopeEvent, ValidateScopeEvent, BeforeValidateFileEvent, ValidateFileEvent, AfterValidateScopeEvent, CompilerPlugin } from './interfaces'; import { StringType, TypedFunctionType, DynamicType, FloatType, IntegerType, InterfaceType, ArrayType, BooleanType, DoubleType, UnionType } from './types'; import { AssociativeArrayType } from './types/AssociativeArrayType'; import { ComponentType } from './types/ComponentType'; @@ -199,24 +199,24 @@ describe('Program', () => { }); it(`emits events for scope and file creation`, () => { - const beforeProgramValidate = sinon.spy(); - const afterProgramValidate = sinon.spy(); - const afterScopeCreate = sinon.spy(); - const beforeScopeValidate = sinon.spy(); - const afterScopeValidate = sinon.spy(); - const beforeFileParse = sinon.spy(); - const afterFileParse = sinon.spy(); - const afterFileValidate = sinon.spy(); + const beforeValidateProgram = sinon.spy(); + const afterValidateProgram = sinon.spy(); + const afterProvideScope = sinon.spy(); + const beforeValidateScope = sinon.spy(); + const afterValidateScope = sinon.spy(); + const beforeProvideFile = sinon.spy(); + const afterProvideFile = sinon.spy(); + const afterValidateFile = sinon.spy(); program.plugins.add({ name: 'emits events for scope and file creation', - beforeProgramValidate: beforeProgramValidate, - afterProgramValidate: afterProgramValidate, - afterScopeCreate: afterScopeCreate, - beforeScopeValidate: beforeScopeValidate, - afterScopeValidate: afterScopeValidate, - beforeFileParse: beforeFileParse, - afterFileParse: afterFileParse, - afterFileValidate: afterFileValidate + beforeValidateProgram: beforeValidateProgram, + afterValidateProgram: afterValidateProgram, + afterProvideScope: afterProvideScope, + beforeValidateScope: beforeValidateScope, + afterValidateScope: afterValidateScope, + beforeProvideFile: beforeProvideFile, + afterProvideFile: afterProvideFile, + afterValidateFile: afterValidateFile }); //add a new source file @@ -230,17 +230,17 @@ describe('Program', () => { program.validate(); //program events - expect(beforeProgramValidate.callCount).to.equal(1); - expect(afterProgramValidate.callCount).to.equal(1); + expect(beforeValidateProgram.callCount).to.equal(1); + expect(afterValidateProgram.callCount).to.equal(1); //scope events //(we get component scope event only because source is created in beforeEach) - expect(afterScopeCreate.callCount).to.equal(1); - expect(beforeScopeValidate.callCount).to.equal(2); - expect(afterScopeValidate.callCount).to.equal(2); + expect(afterProvideScope.callCount).to.equal(1); + expect(beforeValidateScope.callCount).to.equal(2); + expect(afterValidateScope.callCount).to.equal(2); //file events - expect(beforeFileParse.callCount).to.equal(2); - expect(afterFileParse.callCount).to.equal(2); - expect(afterFileValidate.callCount).to.equal(2); + expect(beforeProvideFile.callCount).to.equal(2); + expect(afterProvideFile.callCount).to.equal(2); + expect(afterValidateFile.callCount).to.equal(2); }); }); @@ -299,7 +299,7 @@ describe('Program', () => { let count = 0; const plugin = { name: 'cancel validation', - beforeProgramValidate: () => { + beforeValidateProgram: () => { count++; //if this is the second validate, remove the plugin and change the file to invalidate the scopes again if (count === 2) { @@ -307,7 +307,7 @@ describe('Program', () => { program.setFile('source/test.bs', program.getFile('source/test.bs').fileContents + `'comment`); } }, - afterScopeValidate: () => { + afterValidateScope: () => { //if the diagnostic is avaiable, we know it's safe to cancel if (program.getDiagnostics().find(x => x.code === DiagnosticMessages.cannotFindName('down', 'Direction').code)) { cancel.cancel(); @@ -858,48 +858,48 @@ describe('Program', () => { describe('cancelled', () => { - type eventName = 'beforeFileValidate' | 'onFileValidate' | 'afterFileValidate' | 'beforeScopeValidate' | 'onScopeValidate' | 'afterScopeValidate'; + type eventName = 'beforeValidateFile' | 'validateFile' | 'afterValidateFile' | 'beforeValidateScope' | 'validateScope' | 'afterValidateScope'; interface CancellationPluginOptions { cancelOn?: eventName[]; - eventWhenCancelled?: BeforeFileValidateEvent | OnFileValidateEvent | AfterFileValidateEvent | BeforeScopeValidateEvent | OnScopeValidateEvent | AfterScopeValidateEvent; + eventWhenCancelled?: BeforeValidateFileEvent | ValidateFileEvent | AfterValidateFileEvent | BeforeValidateScopeEvent | ValidateScopeEvent | AfterValidateScopeEvent; cancelTokenSource: CancellationTokenSource; } function getCancellationPlugin(option: CancellationPluginOptions): Plugin { return { name: 'cancelPlugin', - beforeFileValidate: (event) => { - if (option?.cancelOn.includes('beforeFileValidate')) { + beforeValidateFile: (event) => { + if (option?.cancelOn.includes('beforeValidateFile')) { option.eventWhenCancelled = event; option.cancelTokenSource.cancel(); } }, - onFileValidate: (event) => { - if (option?.cancelOn.includes('onFileValidate')) { + validateFile: (event) => { + if (option?.cancelOn.includes('validateFile')) { option.eventWhenCancelled = event; option.cancelTokenSource.cancel(); } }, - afterFileValidate: (event) => { - if (option?.cancelOn.includes('afterFileValidate')) { + afterValidateFile: (event) => { + if (option?.cancelOn.includes('afterValidateFile')) { option.eventWhenCancelled = event; option.cancelTokenSource.cancel(); } }, - beforeScopeValidate: (event) => { - if (option?.cancelOn.includes('beforeScopeValidate')) { + beforeValidateScope: (event) => { + if (option?.cancelOn.includes('beforeValidateScope')) { option.eventWhenCancelled = event; option.cancelTokenSource.cancel(); } }, - onScopeValidate: (event) => { - if (option?.cancelOn.includes('onScopeValidate')) { + validateScope: (event) => { + if (option?.cancelOn.includes('validateScope')) { option.eventWhenCancelled = event; option.cancelTokenSource.cancel(); } }, - afterScopeValidate: (event) => { - if (option?.cancelOn.includes('afterScopeValidate')) { + afterValidateScope: (event) => { + if (option?.cancelOn.includes('afterValidateScope')) { option.eventWhenCancelled = event; option.cancelTokenSource.cancel(); } @@ -925,7 +925,7 @@ describe('Program', () => { // do an initial validation program.validate(); expectZeroDiagnostics(program); - options.cancelOn = ['afterFileValidate']; + options.cancelOn = ['afterValidateFile']; // change file program.setFile('source/file2.bs', ` @@ -935,7 +935,7 @@ describe('Program', () => { `); options.cancelTokenSource = new CancellationTokenSource(); await program.validate({ async: true, cancellationToken: options.cancelTokenSource.token }); - const event = options.eventWhenCancelled as AfterFileValidateEvent; + const event = options.eventWhenCancelled as AfterValidateFileEvent; expect(event).not.to.undefined; expect(event.file.srcPath).includes('file2.bs'); }); @@ -958,7 +958,7 @@ describe('Program', () => { // do an initial validation program.validate(); expectZeroDiagnostics(program); - options.cancelOn = ['beforeScopeValidate']; + options.cancelOn = ['beforeValidateScope']; // change file2 program.setFile('source/file2.bs', ` @@ -979,10 +979,10 @@ describe('Program', () => { end function `); - options.cancelOn = ['onScopeValidate']; + options.cancelOn = ['validateScope']; options.cancelTokenSource = new CancellationTokenSource(); await program.validate({ async: true, cancellationToken: options.cancelTokenSource.token }); - const event = options.eventWhenCancelled as OnScopeValidateEvent; + const event = options.eventWhenCancelled as ValidateScopeEvent; // Event details has info from both changes at scope validation step const srcPaths = event.changedFiles.map(file => file.srcPath); expect(srcPaths.length).to.eq(2); @@ -1834,8 +1834,7 @@ describe('Program', () => { `); }); }); - - it('beforeProgramTranspile sends entries in alphabetical order', async () => { + it('beforeBuildProgram sends entries in alphabetical order', async () => { const destPaths: string[] = []; program.plugins.add({ name: 'test', @@ -1933,7 +1932,7 @@ describe('Program', () => { expect(literalExpression!.tokens.value.text).to.eql('"hello world"'); }); - it('handles Editor for beforeProgramTranspile', async () => { + it('handles Editor for beforeBuildProgram', async () => { const file = program.setFile('source/main.bs', ` sub main() print "hello world" @@ -3027,17 +3026,17 @@ describe('Program', () => { afterProvideFile: (e: AfterProvideFileEvent) => { events.push(`afterProvideFile:${e.destPath}`); }, - beforeFileAdd: (e: BeforeFileAddEvent) => { - events.push(`beforeFileAdd:${e.file.destPath}`); + beforeAddFile: (e: BeforeAddFileEvent) => { + events.push(`beforeAddFile:${e.file.destPath}`); }, - afterFileAdd: (e: AfterFileAddEvent) => { - events.push(`afterFileAdd:${e.file.destPath}`); + afterAddFile: (e: AfterAddFileEvent) => { + events.push(`afterAddFile:${e.file.destPath}`); }, - beforeFileRemove: (e: BeforeFileRemoveEvent) => { - events.push(`beforeFileRemove:${e.file.destPath}`); + beforeRemoveFile: (e: BeforeRemoveFileEvent) => { + events.push(`beforeRemoveFile:${e.file.destPath}`); }, - afterFileRemove: (e: AfterFileRemoveEvent) => { - events.push(`afterFileRemove:${e.file.destPath}`); + afterRemoveFile: (e: AfterRemoveFileEvent) => { + events.push(`afterRemoveFile:${e.file.destPath}`); } }; program.plugins.add(plugin); @@ -3049,14 +3048,14 @@ describe('Program', () => { 'beforeProvideFile:' + s('source/buttons.component.bs'), 'provideFile:' + s('source/buttons.component.bs'), 'afterProvideFile:' + s('source/buttons.component.bs'), - 'beforeFileAdd:' + s('source/buttons.component.bs'), - 'afterFileAdd:' + s('source/buttons.component.bs'), - 'beforeFileAdd:' + s('source/buttons.component.bs.two'), - 'afterFileAdd:' + s('source/buttons.component.bs.two'), - 'beforeFileRemove:' + s('source/buttons.component.bs'), - 'afterFileRemove:' + s('source/buttons.component.bs'), - 'beforeFileRemove:' + s('source/buttons.component.bs.two'), - 'afterFileRemove:' + s('source/buttons.component.bs.two') + 'beforeAddFile:' + s('source/buttons.component.bs'), + 'afterAddFile:' + s('source/buttons.component.bs'), + 'beforeAddFile:' + s('source/buttons.component.bs.two'), + 'afterAddFile:' + s('source/buttons.component.bs.two'), + 'beforeRemoveFile:' + s('source/buttons.component.bs'), + 'afterRemoveFile:' + s('source/buttons.component.bs'), + 'beforeRemoveFile:' + s('source/buttons.component.bs.two'), + 'afterRemoveFile:' + s('source/buttons.component.bs.two') ]); }); @@ -3075,11 +3074,11 @@ describe('Program', () => { }) ); }, - beforeFileRemove: (e: BeforeFileRemoveEvent) => { - events.push(`beforeFileRemove:${e.file.destPath}`); + beforeRemoveFile: (e: BeforeRemoveFileEvent) => { + events.push(`beforeRemoveFile:${e.file.destPath}`); }, - afterFileRemove: (e: AfterFileRemoveEvent) => { - events.push(`afterFileRemove:${e.file.destPath}`); + afterRemoveFile: (e: AfterRemoveFileEvent) => { + events.push(`afterRemoveFile:${e.file.destPath}`); } }; program.plugins.add(plugin); @@ -3093,51 +3092,51 @@ describe('Program', () => { //we should only have one set of events per file expect(events).to.eql([ - 'beforeFileRemove:' + s('source/buttons.component.bs.two'), - 'afterFileRemove:' + s('source/buttons.component.bs.two'), - 'beforeFileRemove:' + s('source/buttons.component.bs'), - 'afterFileRemove:' + s('source/buttons.component.bs') + 'beforeRemoveFile:' + s('source/buttons.component.bs.two'), + 'afterRemoveFile:' + s('source/buttons.component.bs.two'), + 'beforeRemoveFile:' + s('source/buttons.component.bs'), + 'afterRemoveFile:' + s('source/buttons.component.bs') ]); }); it('emits file validation events', () => { const plugin = { name: 'test', - beforeFileValidate: sinon.spy(), - onFileValidate: sinon.spy(), - afterFileValidate: sinon.spy() + beforeValidateFile: sinon.spy(), + validateFile: sinon.spy(), + afterValidateFile: sinon.spy() }; program.plugins.add(plugin); program.setFile('source/main.brs', ''); program.validate(); - expect(plugin.beforeFileValidate.callCount).to.equal(1); - expect(plugin.onFileValidate.callCount).to.equal(1); - expect(plugin.afterFileValidate.callCount).to.equal(1); + expect(plugin.beforeValidateFile.callCount).to.equal(1); + expect(plugin.validateFile.callCount).to.equal(1); + expect(plugin.afterValidateFile.callCount).to.equal(1); }); it('emits file validation events', () => { const plugin = { name: 'test', - beforeFileValidate: sinon.spy(), - onFileValidate: sinon.spy(), - afterFileValidate: sinon.spy() + beforeValidateFile: sinon.spy(), + validateFile: sinon.spy(), + afterValidateFile: sinon.spy() }; program.plugins.add(plugin); program.setFile('components/main.xml', ''); program.validate(); - expect(plugin.beforeFileValidate.callCount).to.equal(1); - expect(plugin.onFileValidate.callCount).to.equal(1); - expect(plugin.afterFileValidate.callCount).to.equal(1); + expect(plugin.beforeValidateFile.callCount).to.equal(1); + expect(plugin.validateFile.callCount).to.equal(1); + expect(plugin.afterValidateFile.callCount).to.equal(1); }); it('emits program dispose event', () => { const plugin = { name: 'test', - beforeProgramDispose: sinon.spy() + beforeRemoveProgram: sinon.spy() }; program.plugins.add(plugin); program.dispose(); - expect(plugin.beforeProgramDispose.callCount).to.equal(1); + expect(plugin.beforeRemoveProgram.callCount).to.equal(1); }); }); diff --git a/src/Program.ts b/src/Program.ts index e31c2bd6f..fedf1d53f 100644 --- a/src/Program.ts +++ b/src/Program.ts @@ -6,7 +6,7 @@ import { CancellationTokenSource } from 'vscode-languageserver'; import type { BsConfig, FinalizedBsConfig } from './BsConfig'; import { Scope } from './Scope'; import { DiagnosticMessages } from './DiagnosticMessages'; -import type { FileObj, SemanticToken, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, ProvideDefinitionEvent, ProvideReferencesEvent, ProvideDocumentSymbolsEvent, ProvideWorkspaceSymbolsEvent, BeforeFileAddEvent, BeforeFileRemoveEvent, PrepareFileEvent, PrepareProgramEvent, ProvideFileEvent, SerializedFile, TranspileObj, SerializeFileEvent, ScopeValidationOptions, ExtraSymbolData } from './interfaces'; +import type { FileObj, SemanticToken, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, ProvideDefinitionEvent, ProvideReferencesEvent, ProvideDocumentSymbolsEvent, ProvideWorkspaceSymbolsEvent, BeforeAddFileEvent, BeforeRemoveFileEvent, PrepareFileEvent, PrepareProgramEvent, ProvideFileEvent, SerializedFile, TranspileObj, SerializeFileEvent, ScopeValidationOptions, ExtraSymbolData } from './interfaces'; import { standardizePath as s, util } from './util'; import { XmlScope } from './XmlScope'; import { DependencyGraph } from './DependencyGraph'; @@ -615,17 +615,17 @@ export class Program { * Update internal maps with this file reference */ private assignFile(file: T) { - const fileAddEvent: BeforeFileAddEvent = { + const fileAddEvent: BeforeAddFileEvent = { file: file, program: this }; - this.plugins.emit('beforeFileAdd', fileAddEvent); + this.plugins.emit('beforeAddFile', fileAddEvent); this.files[file.srcPath.toLowerCase()] = file; this.destMap.set(file.destPath.toLowerCase(), file); - this.plugins.emit('afterFileAdd', fileAddEvent); + this.plugins.emit('afterAddFile', fileAddEvent); return file; } @@ -732,15 +732,23 @@ export class Program { //if this is an xml file in the components folder, register it as a component if (this.isComponentsXmlFile(file)) { + this.plugins.emit('beforeProvideScope', { + program: this, + scope: undefined + }); //create a new scope for this xml file let scope = new XmlScope(file, this); this.addScope(scope); //register this componet now that we have parsed it and know its component name this.registerComponent(file, scope); + this.plugins.emit('provideScope', { + program: this, + scope: scope + }); //notify plugins that the scope is created and the component is registered - this.plugins.emit('afterScopeCreate', { + this.plugins.emit('afterProvideScope', { program: this, scope: scope }); @@ -839,7 +847,7 @@ export class Program { const sourceScope = new Scope('source', this, 'scope:source'); sourceScope.attachDependencyGraph(this.dependencyGraph); this.addScope(sourceScope); - this.plugins.emit('afterScopeCreate', { + this.plugins.emit('afterProvideScope', { program: this, scope: sourceScope }); @@ -876,24 +884,24 @@ export class Program { } this.diagnostics.clearForFile(file.srcPath); - const event: BeforeFileRemoveEvent = { file: file, program: this }; - this.plugins.emit('beforeFileRemove', event); + const event: BeforeRemoveFileEvent = { file: file, program: this }; + this.plugins.emit('beforeRemoveFile', event); //if there is a scope named the same as this file's path, remove it (i.e. xml scopes) let scope = this.scopes[file.destPath]; if (scope) { this.logger.debug('Removing associated scope', scope.name); - const scopeDisposeEvent = { + const scopeRemoveEvent = { program: this, scope: scope }; - this.plugins.emit('beforeScopeDispose', scopeDisposeEvent); - this.plugins.emit('onScopeDispose', scopeDisposeEvent); + this.plugins.emit('beforeRemoveScope', scopeRemoveEvent); + this.plugins.emit('removeScope', scopeRemoveEvent); scope.dispose(); //notify dependencies of this scope that it has been removed this.dependencyGraph.remove(scope.dependencyGraphKey!); this.removeScope(this.scopes[file.destPath]); - this.plugins.emit('afterScopeDispose', scopeDisposeEvent); + this.plugins.emit('afterRemoveScope', scopeRemoveEvent); } //remove the file from the program this.unassignFile(file); @@ -930,7 +938,7 @@ export class Program { //dispose file file?.dispose?.(); - this.plugins.emit('afterFileRemove', event); + this.plugins.emit('afterRemoveFile', event); } } @@ -1002,7 +1010,7 @@ export class Program { throw new Error('Cannot run synchronous validation while an async validation is in progress'); } - let beforeProgramValidateWasEmitted = false; + let beforeValidateProgramWasEmitted = false; const brsFilesValidated: BrsFile[] = this.validationDetails.brsFilesValidated; const xmlFilesValidated: XmlFile[] = this.validationDetails.xmlFilesValidated; @@ -1035,11 +1043,11 @@ export class Program { .once('before and on programValidate', () => { logValidateEnd = this.logger.timeStart(LogLevel.log, `Validating project${(this.logger.logLevel as LogLevel) > LogLevel.log ? ` (run ${validationRunId})` : ''}`); this.diagnostics.clearForTag(ProgramValidatorDiagnosticsTag); - this.plugins.emit('beforeProgramValidate', { + this.plugins.emit('beforeValidateProgram', { program: this }); - beforeProgramValidateWasEmitted = true; - this.plugins.emit('onProgramValidate', { + beforeValidateProgramWasEmitted = true; + this.plugins.emit('validateProgram', { program: this }); }) @@ -1055,16 +1063,16 @@ export class Program { this.addComponentReferenceType(componentKey, componentName); } }) - .forEach('beforeFileValidate', () => filesToProcess, (file) => { + .forEach('beforeValidateFile', () => filesToProcess, (file) => { //run the beforeFilevalidate event for every unvalidated file - this.plugins.emit('beforeFileValidate', { + this.plugins.emit('beforeValidateFile', { program: this, file: file }); }) - .forEach('onFileValidate', () => filesToProcess, (file) => { - //run the onFileValidate event for every unvalidated file - this.plugins.emit('onFileValidate', { + .forEach('validateFile', () => filesToProcess, (file) => { + //run the validateFile event for every unvalidated file + this.plugins.emit('validateFile', { program: this, file: file }); @@ -1075,9 +1083,9 @@ export class Program { xmlFilesValidated.push(file); } }) - .forEach('afterFileValidate', () => filesToProcess, (file) => { - //run the onFileValidate event for every unvalidated file - this.plugins.emit('afterFileValidate', { + .forEach('afterValidateFile', () => filesToProcess, (file) => { + //run the validateFile event for every unvalidated file + this.plugins.emit('afterValidateFile', { program: this, file: file }); @@ -1234,7 +1242,7 @@ export class Program { this.lastValidationInfo.scopeNames = new Set(scopesToValidate.map(s => s.name?.toLowerCase() ?? '')); }) .forEach('beforeScopeValidate', () => scopesToValidate, (scope) => { - this.plugins.emit('beforeScopeValidate', { + this.plugins.emit('beforeValidateScope', { program: this, scope: scope }); @@ -1242,8 +1250,8 @@ export class Program { .forEach('validate scope', () => scopesToValidate, (scope) => { scope.validate(this.currentScopeValidationOptions); }) - .forEach('afterScopeValidate', () => scopesToValidate, (scope) => { - this.plugins.emit('afterScopeValidate', { + .forEach('afterValidateScope', () => scopesToValidate, (scope) => { + this.plugins.emit('afterValidateScope', { program: this, scope: scope }); @@ -1265,10 +1273,10 @@ export class Program { logValidateEnd(); }) .onComplete(() => { - //if we emitted the beforeProgramValidate hook, emit the afterProgramValidate hook as well - if (beforeProgramValidateWasEmitted) { + //if we emitted the beforeValidateProgram hook, emit the afterValidateProgram hook as well + if (beforeValidateProgramWasEmitted) { const wasCancelled = options?.cancellationToken?.isCancellationRequested ?? false; - this.plugins.emit('afterProgramValidate', { + this.plugins.emit('afterValidateProgram', { program: this, wasCancelled: wasCancelled }); @@ -1691,7 +1699,25 @@ export class Program { const scopes = this.getScopesForFile(file); - this.plugins.emit('onGetCodeActions', { + this.plugins.emit('beforeProvideCodeActions', { + program: this, + file: file, + range: range, + diagnostics: diagnostics, + scopes: scopes, + codeActions: codeActions + }); + + this.plugins.emit('provideCodeActions', { + program: this, + file: file, + range: range, + diagnostics: diagnostics, + scopes: scopes, + codeActions: codeActions + }); + + this.plugins.emit('afterProvideCodeActions', { program: this, file: file, range: range, @@ -1709,8 +1735,20 @@ export class Program { public getSemanticTokens(srcPath: string): SemanticToken[] | undefined { const file = this.getFile(srcPath); if (file) { + this.plugins.emit('beforeProvideSemanticTokens', { + program: this, + file: file, + scopes: this.getScopesForFile(file), + semanticTokens: undefined + }); const result = [] as SemanticToken[]; - this.plugins.emit('onGetSemanticTokens', { + this.plugins.emit('provideSemanticTokens', { + program: this, + file: file, + scopes: this.getScopesForFile(file), + semanticTokens: result + }); + this.plugins.emit('afterProvideSemanticTokens', { program: this, file: file, scopes: this.getScopesForFile(file), @@ -1912,7 +1950,7 @@ export class Program { files: files, result: allFiles }); - await this.plugins.emitAsync('onSerializeProgram', serializeProgramEvent); + await this.plugins.emitAsync('serializeProgram', serializeProgramEvent); // serialize each file for (const file of files) { @@ -2173,7 +2211,7 @@ export class Program { } public dispose() { - this.plugins.emit('beforeProgramDispose', { program: this }); + this.plugins.emit('beforeRemoveProgram', { program: this }); for (let filePath in this.files) { this.files[filePath]?.dispose?.(); @@ -2183,6 +2221,8 @@ export class Program { } this.globalScope?.dispose?.(); this.dependencyGraph?.dispose?.(); + this.plugins.emit('removeProgram', { program: this }); + this.plugins.emit('afterRemoveProgram', { program: this }); } } diff --git a/src/ProgramBuilder.spec.ts b/src/ProgramBuilder.spec.ts index e29160eaa..b9daf2e07 100644 --- a/src/ProgramBuilder.spec.ts +++ b/src/ProgramBuilder.spec.ts @@ -14,7 +14,7 @@ import type { BsConfig } from './BsConfig'; import type { BscFile } from './files/BscFile'; import { tempDir, rootDir, outDir } from './testHelpers.spec'; import { Deferred } from './deferred'; -import type { AfterProgramCreateEvent, BsDiagnostic } from './interfaces'; +import type { AfterProvideProgramEvent, BsDiagnostic } from './interfaces'; describe('ProgramBuilder', () => { @@ -60,12 +60,12 @@ describe('ProgramBuilder', () => { ).to.eql(0); }); - it('includes .program in the afterProgramCreate event', async () => { + it('includes .program in the afterProvideProgram event', async () => { builder = new ProgramBuilder(); const deferred = new Deferred(); builder.plugins.add({ name: 'test', - afterProgramCreate: () => { + afterProvideProgram: () => { deferred.resolve(builder.program); } }); @@ -76,12 +76,12 @@ describe('ProgramBuilder', () => { }); - it('can edit files array in afterProgramCreate event', async () => { + it('can edit files array in afterProvideProgram event', async () => { builder = new ProgramBuilder(); const deferred = new Deferred(); builder.plugins.add({ name: 'test', - afterProgramCreate: (event: AfterProgramCreateEvent) => { + afterProvideProgram: (event: AfterProvideProgramEvent) => { event.program.options.files.push('other/**/*.*'); deferred.resolve(builder.program); @@ -327,16 +327,16 @@ describe('ProgramBuilder', () => { }); it('forwards program events', async () => { - const beforeProgramValidate = sinon.spy(); - const afterProgramValidate = sinon.spy(); + const beforeValidateProgram = sinon.spy(); + const afterValidateProgram = sinon.spy(); builder.plugins.add({ name: 'forwards program events', - beforeProgramValidate: beforeProgramValidate, - afterProgramValidate: afterProgramValidate + beforeValidateProgram: beforeValidateProgram, + afterValidateProgram: afterValidateProgram }); await builder.run({}); - expect(beforeProgramValidate.callCount).to.equal(1); - expect(afterProgramValidate.callCount).to.equal(1); + expect(beforeValidateProgram.callCount).to.equal(1); + expect(afterValidateProgram.callCount).to.equal(1); }); diff --git a/src/ProgramBuilder.ts b/src/ProgramBuilder.ts index 14e713c0a..48b9e2807 100644 --- a/src/ProgramBuilder.ts +++ b/src/ProgramBuilder.ts @@ -173,8 +173,12 @@ export class ProgramBuilder { protected createProgram() { this.program = new Program(this.options, this.logger, this.plugins, this.diagnostics); + this.plugins.emit('provideProgram', { + builder: this, + program: this.program + }); - this.plugins.emit('afterProgramCreate', { + this.plugins.emit('afterProvideProgram', { builder: this, program: this.program }); @@ -194,7 +198,7 @@ export class ProgramBuilder { this.plugins.add(plugin); } - this.plugins.emit('beforeProgramCreate', { + this.plugins.emit('beforeProvideProgram', { builder: this }); } diff --git a/src/Scope.spec.ts b/src/Scope.spec.ts index 70d0e8538..68c5f9483 100644 --- a/src/Scope.spec.ts +++ b/src/Scope.spec.ts @@ -8,7 +8,7 @@ import PluginInterface from './PluginInterface'; import { expectDiagnostics, expectDiagnosticsIncludes, expectTypeToBe, expectZeroDiagnostics, trim } from './testHelpers.spec'; import type { BrsFile } from './files/BrsFile'; import type { AssignmentStatement, ForEachStatement, IfStatement, NamespaceStatement, PrintStatement } from './parser/Statement'; -import type { CompilerPlugin, OnScopeValidateEvent } from './interfaces'; +import type { CompilerPlugin, ValidateScopeEvent } from './interfaces'; import { SymbolTypeFlag } from './SymbolTypeFlag'; import { EnumMemberType, EnumType } from './types/EnumType'; import { ClassType } from './types/ClassType'; @@ -1274,25 +1274,25 @@ describe('Scope', () => { program.plugins = new PluginInterface(); const plugin = program.plugins.add({ name: 'Emits validation events', - beforeScopeValidate: sinon.spy(), - onScopeValidate: sinon.spy(), - afterScopeValidate: sinon.spy() + beforeValidateScope: sinon.spy(), + validateScope: sinon.spy(), + afterValidateScope: sinon.spy() }); program.validate(); let scopeNames = program.getScopes().map(x => x.name).filter(x => x !== 'global').sort(); - const scopes = plugin.beforeScopeValidate.getCalls().map(x => x.args[0].scope); - expect(plugin.beforeScopeValidate.callCount).to.equal(2); + const scopes = plugin.beforeValidateScope.getCalls().map(x => x.args[0].scope); + expect(plugin.beforeValidateScope.callCount).to.equal(2); expect(scopes).to.include(sourceScope); expect(scopes).to.include(compScope); - expect(plugin.onScopeValidate.callCount).to.equal(2); - expect(plugin.onScopeValidate.getCalls().map( - x => (x.args[0] as OnScopeValidateEvent).scope.name + expect(plugin.validateScope.callCount).to.equal(2); + expect(plugin.validateScope.getCalls().map( + x => (x.args[0] as ValidateScopeEvent).scope.name ).sort()).to.eql(scopeNames); scopeNames = program.getScopes().map(x => x.name).filter(x => x !== 'global').sort(); - expect(plugin.afterScopeValidate.callCount).to.equal(2); + expect(plugin.afterValidateScope.callCount).to.equal(2); expect(scopes).to.include(sourceScope); expect(scopes).to.include(compScope); }); @@ -4792,7 +4792,7 @@ describe('Scope', () => { let changedSymbolsSize = -1; class TestScopeValidator implements CompilerPlugin { name = 'TestScopeValidator'; - public onScopeValidate(event: OnScopeValidateEvent) { + public validateScope(event: ValidateScopeEvent) { changedSymbolsSize = event.changedSymbols.get(SymbolTypeFlag.runtime).size; } } @@ -4821,7 +4821,7 @@ describe('Scope', () => { let symbolChanges: Map>; class TestScopeValidator implements CompilerPlugin { name = 'TestScopeValidator'; - public onScopeValidate(event: OnScopeValidateEvent) { + public validateScope(event: ValidateScopeEvent) { symbolChanges = event.changedSymbols; } } @@ -4871,7 +4871,7 @@ describe('Scope', () => { let symbolChanges: Map>; class TestScopeValidator implements CompilerPlugin { name = 'TestScopeValidator'; - public onScopeValidate(event: OnScopeValidateEvent) { + public validateScope(event: ValidateScopeEvent) { symbolChanges = event.changedSymbols; } } diff --git a/src/Scope.ts b/src/Scope.ts index 1ae1e59f7..59e58bef0 100644 --- a/src/Scope.ts +++ b/src/Scope.ts @@ -827,7 +827,7 @@ export class Scope { changedSymbols: validationOptions?.changedSymbols }; t0 = performance.now(); - this.program.plugins.emit('onScopeValidate', scopeValidateEvent); + this.program.plugins.emit('validateScope', scopeValidateEvent); this.validationMetrics.validationTime = performance.now() - t0; //unlink all symbol tables from this scope (so they don't accidentally stick around) this.unlinkSymbolTable(); diff --git a/src/bscPlugin/BscPlugin.ts b/src/bscPlugin/BscPlugin.ts index 694d10b18..573c958ce 100644 --- a/src/bscPlugin/BscPlugin.ts +++ b/src/bscPlugin/BscPlugin.ts @@ -1,5 +1,5 @@ import { isBrsFile, isXmlFile } from '../astUtils/reflection'; -import type { Plugin, OnFileValidateEvent, OnGetCodeActionsEvent, ProvideHoverEvent, OnGetSemanticTokensEvent, OnScopeValidateEvent, ProvideCompletionsEvent, ProvideDefinitionEvent, ProvideReferencesEvent, ProvideDocumentSymbolsEvent, ProvideWorkspaceSymbolsEvent, AfterProvideFileEvent, AfterFileValidateEvent, AfterProgramValidateEvent, AfterSerializeFileEvent, BeforeBuildProgramEvent, OnPrepareFileEvent, WriteFileEvent } from '../interfaces'; +import type { Plugin, ValidateFileEvent, ProvideCodeActionsEvent, ProvideHoverEvent, ProvideSemanticTokensEvent, ValidateScopeEvent, ProvideCompletionsEvent, ProvideDefinitionEvent, ProvideReferencesEvent, ProvideDocumentSymbolsEvent, ProvideWorkspaceSymbolsEvent, AfterProvideFileEvent, AfterValidateFileEvent, AfterValidateProgramEvent, AfterSerializeFileEvent, BeforeBuildProgramEvent, OnPrepareFileEvent, WriteFileEvent } from '../interfaces'; import { CodeActionsProcessor } from './codeActions/CodeActionsProcessor'; import { CompletionsProcessor } from './completions/CompletionsProcessor'; import { DefinitionProvider } from './definition/DefinitionProvider'; @@ -29,7 +29,7 @@ export class BscPlugin implements Plugin { new FileProvider(event).process(); } - public onGetCodeActions(event: OnGetCodeActionsEvent) { + public provideCodeActions(event: ProvideCodeActionsEvent) { new CodeActionsProcessor(event).process(); } @@ -57,33 +57,33 @@ export class BscPlugin implements Plugin { new ReferencesProvider(event).process(); } - public onGetSemanticTokens(event: OnGetSemanticTokensEvent) { + public provideSemanticTokens(event: ProvideSemanticTokensEvent) { if (isBrsFile(event.file)) { return new BrsFileSemanticTokensProcessor(event as any).process(); } } - public onFileValidate(event: OnFileValidateEvent) { + public validateFile(event: ValidateFileEvent) { if (isBrsFile(event.file)) { - return new BrsFileValidator(event as OnFileValidateEvent).process(); + return new BrsFileValidator(event as ValidateFileEvent).process(); } else if (isXmlFile(event.file)) { - return new XmlFileValidator(event as OnFileValidateEvent).process(); + return new XmlFileValidator(event as ValidateFileEvent).process(); } } - public afterFileValidate(event: AfterFileValidateEvent) { + public afterValidateFile(event: AfterValidateFileEvent) { if (isBrsFile(event.file)) { - return new BrsFileAfterValidator(event as AfterFileValidateEvent).process(); + return new BrsFileAfterValidator(event as AfterValidateFileEvent).process(); } } private scopeValidator = new ScopeValidator(); - public onScopeValidate(event: OnScopeValidateEvent) { + public validateScope(event: ValidateScopeEvent) { this.scopeValidator.processEvent(event); } - public afterProgramValidate(event: AfterProgramValidateEvent) { + public afterValidateProgram(event: AfterValidateProgramEvent) { new ProgramValidator(event).process(); //release memory once the validation cycle has finished this.scopeValidator.reset(); diff --git a/src/bscPlugin/codeActions/CodeActionsProcessor.ts b/src/bscPlugin/codeActions/CodeActionsProcessor.ts index e0701c346..453b9fac4 100644 --- a/src/bscPlugin/codeActions/CodeActionsProcessor.ts +++ b/src/bscPlugin/codeActions/CodeActionsProcessor.ts @@ -5,7 +5,7 @@ import { DiagnosticCodeMap } from '../../DiagnosticMessages'; import type { BrsFile } from '../../files/BrsFile'; import type { BscFile } from '../../files/BscFile'; import type { XmlFile } from '../../files/XmlFile'; -import type { BsDiagnostic, OnGetCodeActionsEvent } from '../../interfaces'; +import type { BsDiagnostic, ProvideCodeActionsEvent } from '../../interfaces'; import { ParseMode } from '../../parser/Parser'; import { util } from '../../util'; import { isBrsFile, isFunctionExpression, isVariableExpression, isVoidType } from '../../astUtils/reflection'; @@ -15,7 +15,7 @@ import { SymbolTypeFlag } from '../../SymbolTypeFlag'; export class CodeActionsProcessor { public constructor( - public event: OnGetCodeActionsEvent + public event: ProvideCodeActionsEvent ) { } diff --git a/src/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.ts b/src/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.ts index f031686d8..52a55a920 100644 --- a/src/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.ts +++ b/src/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.ts @@ -2,7 +2,7 @@ import { SemanticTokenModifiers } from 'vscode-languageserver-protocol'; import { SemanticTokenTypes } from 'vscode-languageserver-protocol'; import { isCallableType, isClassType, isComponentType, isConstStatement, isDottedGetExpression, isDynamicType, isEnumMemberType, isEnumType, isFunctionExpression, isFunctionStatement, isInterfaceType, isNamespaceType, isPrimitiveType, isVariableExpression } from '../../astUtils/reflection'; import type { BrsFile } from '../../files/BrsFile'; -import type { ExtraSymbolData, OnGetSemanticTokensEvent, SemanticToken, TypeChainEntry } from '../../interfaces'; +import type { ExtraSymbolData, ProvideSemanticTokensEvent, SemanticToken, TypeChainEntry } from '../../interfaces'; import type { Locatable, Token } from '../../lexer/Token'; import util from '../../util'; import { SymbolTypeFlag } from '../../SymbolTypeFlag'; @@ -12,7 +12,7 @@ import type { AstNode } from '../../parser/AstNode'; export class BrsFileSemanticTokensProcessor { public constructor( - public event: OnGetSemanticTokensEvent + public event: ProvideSemanticTokensEvent ) { } diff --git a/src/bscPlugin/validation/BrsFileAfterValidator.ts b/src/bscPlugin/validation/BrsFileAfterValidator.ts index a649ae739..1ee236ab2 100644 --- a/src/bscPlugin/validation/BrsFileAfterValidator.ts +++ b/src/bscPlugin/validation/BrsFileAfterValidator.ts @@ -1,12 +1,12 @@ import { isBrsFile } from '../../astUtils/reflection'; import type { BrsFile } from '../../files/BrsFile'; -import type { AfterFileValidateEvent } from '../../interfaces'; +import type { AfterValidateFileEvent } from '../../interfaces'; export class BrsFileAfterValidator { constructor( - public event: AfterFileValidateEvent + public event: AfterValidateFileEvent ) { } diff --git a/src/bscPlugin/validation/BrsFileValidator.ts b/src/bscPlugin/validation/BrsFileValidator.ts index 2c7bc0872..18add9ca2 100644 --- a/src/bscPlugin/validation/BrsFileValidator.ts +++ b/src/bscPlugin/validation/BrsFileValidator.ts @@ -2,7 +2,7 @@ import { isAliasStatement, isBlock, isBody, isClassStatement, isConditionalCompi import { createVisitor, WalkMode } from '../../astUtils/visitors'; import { DiagnosticMessages } from '../../DiagnosticMessages'; import type { BrsFile } from '../../files/BrsFile'; -import type { ExtraSymbolData, OnFileValidateEvent } from '../../interfaces'; +import type { ExtraSymbolData, ValidateFileEvent } from '../../interfaces'; import { TokenKind } from '../../lexer/TokenKind'; import type { AstNode, Expression, Statement } from '../../parser/AstNode'; import { CallExpression, type FunctionExpression, type LiteralExpression } from '../../parser/Expression'; @@ -21,7 +21,7 @@ import { TypeStatementType } from '../../types/TypeStatementType'; export class BrsFileValidator { constructor( - public event: OnFileValidateEvent + public event: ValidateFileEvent ) { } diff --git a/src/bscPlugin/validation/ProgramValidator.ts b/src/bscPlugin/validation/ProgramValidator.ts index 9ecce5a74..f5eed5c46 100644 --- a/src/bscPlugin/validation/ProgramValidator.ts +++ b/src/bscPlugin/validation/ProgramValidator.ts @@ -1,13 +1,13 @@ import { isBrsFile } from '../../astUtils/reflection'; import { DiagnosticMessages } from '../../DiagnosticMessages'; -import type { AfterProgramValidateEvent } from '../../interfaces'; +import type { AfterValidateProgramEvent } from '../../interfaces'; import util from '../../util'; export const ProgramValidatorDiagnosticsTag = 'ProgramValidator'; export class ProgramValidator { constructor( - private event: AfterProgramValidateEvent + private event: AfterValidateProgramEvent ) { } public process() { diff --git a/src/bscPlugin/validation/ScopeValidator.spec.ts b/src/bscPlugin/validation/ScopeValidator.spec.ts index 191a8e163..607de3aa4 100644 --- a/src/bscPlugin/validation/ScopeValidator.spec.ts +++ b/src/bscPlugin/validation/ScopeValidator.spec.ts @@ -4386,8 +4386,8 @@ describe('ScopeValidator', () => { file: file }; //emit an event to allow plugins to contribute to the file validation process - program.plugins.emit('onFileValidate', validateFileEvent); - program.plugins.emit('afterFileValidate', validateFileEvent); + program.plugins.emit('validateFile', validateFileEvent); + program.plugins.emit('afterValidateFile', validateFileEvent); } const commonContents = ` diff --git a/src/bscPlugin/validation/ScopeValidator.ts b/src/bscPlugin/validation/ScopeValidator.ts index e2d7bd178..bc9d45c65 100644 --- a/src/bscPlugin/validation/ScopeValidator.ts +++ b/src/bscPlugin/validation/ScopeValidator.ts @@ -3,7 +3,7 @@ import { isAliasStatement, isAssignmentStatement, isAssociativeArrayType, isBina import type { DiagnosticInfo } from '../../DiagnosticMessages'; import { DiagnosticMessages } from '../../DiagnosticMessages'; import type { BrsFile } from '../../files/BrsFile'; -import type { BsDiagnostic, CallableContainer, ExtraSymbolData, FileReference, GetTypeOptions, OnScopeValidateEvent, TypeChainEntry, TypeChainProcessResult, TypeCompatibilityData } from '../../interfaces'; +import type { BsDiagnostic, CallableContainer, ExtraSymbolData, FileReference, GetTypeOptions, ValidateScopeEvent, TypeChainEntry, TypeChainProcessResult, TypeCompatibilityData } from '../../interfaces'; import { SymbolTypeFlag } from '../../SymbolTypeFlag'; import type { AssignmentStatement, AugmentedAssignmentStatement, ClassStatement, DottedSetStatement, IncrementStatement, NamespaceStatement, ReturnStatement } from '../../parser/Statement'; import { util } from '../../util'; @@ -58,7 +58,7 @@ const enum ScopeValidatorDiagnosticTag { /** * A validator that handles all scope validations for a program validation cycle. - * You should create ONE of these to handle all scope events between beforeProgramValidate and afterProgramValidate, + * You should create ONE of these to handle all scope events between beforeValidateProgram and afterValidateProgram, * and call reset() before using it again in the next cycle */ export class ScopeValidator { @@ -66,12 +66,12 @@ export class ScopeValidator { /** * The event currently being processed. This will change multiple times throughout the lifetime of this validator */ - private event: OnScopeValidateEvent; + private event: ValidateScopeEvent; private segmentsMetrics = new Map(); private validationKindsMetrics = new Map(); - public processEvent(event: OnScopeValidateEvent) { + public processEvent(event: ValidateScopeEvent) { this.event = event; if (this.event.program.globalScope === this.event.scope) { return; diff --git a/src/bscPlugin/validation/XmlFileValidator.ts b/src/bscPlugin/validation/XmlFileValidator.ts index 49dbf8a05..2cd2fb638 100644 --- a/src/bscPlugin/validation/XmlFileValidator.ts +++ b/src/bscPlugin/validation/XmlFileValidator.ts @@ -1,12 +1,12 @@ import { DiagnosticMessages } from '../../DiagnosticMessages'; import type { XmlFile } from '../../files/XmlFile'; -import type { OnFileValidateEvent } from '../../interfaces'; +import type { ValidateFileEvent } from '../../interfaces'; import type { SGAst } from '../../parser/SGTypes'; import util from '../../util'; export class XmlFileValidator { constructor( - public event: OnFileValidateEvent + public event: ValidateFileEvent ) { } diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts index 341a26245..be054b026 100644 --- a/src/files/BrsFile.spec.ts +++ b/src/files/BrsFile.spec.ts @@ -42,10 +42,10 @@ describe('BrsFile', () => { function validateFile(...files: BrsFile[]) { for (const file of files) { - program.plugins.emit('onFileValidate', { program: program, file: file }); + program.plugins.emit('validateFile', { program: program, file: file }); } for (const file of files) { - program.plugins.emit('afterFileValidate', { program: program, file: file }); + program.plugins.emit('afterValidateFile', { program: program, file: file }); } } @@ -4489,7 +4489,7 @@ describe('BrsFile', () => { }); program.plugins.add({ name: 'transform callback', - afterFileParse: onParsed + afterProvideFile: onParsed }); file = program.setFile(`source/file${ext}`, ` sub Sum() @@ -5192,7 +5192,7 @@ describe('BrsFile', () => { program: program, file: mainFile }; - program.plugins.emit('onFileValidate', validateFileEvent); + program.plugins.emit('validateFile', validateFileEvent); expect(mainFile.requiredSymbols.length).to.eq(0); }); @@ -5218,7 +5218,7 @@ describe('BrsFile', () => { program: program, file: mainFile }; - program.plugins.emit('onFileValidate', validateFileEvent); + program.plugins.emit('validateFile', validateFileEvent); expect(mainFile.requiredSymbols.length).to.eq(0); }); @@ -5237,7 +5237,7 @@ describe('BrsFile', () => { program: program, file: mainFile }; - program.plugins.emit('onFileValidate', validateFileEvent); + program.plugins.emit('validateFile', validateFileEvent); expect(mainFile.requiredSymbols.length).to.eq(0); }); @@ -5969,7 +5969,7 @@ describe('BrsFile', () => { west = "W" end enum `); - program.plugins.emit('onFileValidate', { program: program, file: mainFile }); + program.plugins.emit('validateFile', { program: program, file: mainFile }); runtimeSymbols = mainFile.providedSymbols.symbolMap.get(SymbolTypeFlag.runtime); expect(runtimeSymbols.size).to.eq(1); let runtimeChanges = mainFile.providedSymbols.changes.get(SymbolTypeFlag.runtime); diff --git a/src/files/XmlFile.spec.ts b/src/files/XmlFile.spec.ts index 6ba54e2dd..df633cca9 100644 --- a/src/files/XmlFile.spec.ts +++ b/src/files/XmlFile.spec.ts @@ -62,7 +62,7 @@ describe('XmlFile', () => { const file = new XmlFile({ srcPath: 'abs', destPath: 'rel', program: program }); program.plugins.add({ name: 'allows modifying the parsed XML model', - afterFileParse: () => { + afterProvideFile: () => { let child = file.parser.ast.componentElement!.childrenElement.elements[0]; expect(child.attributes).to.have.lengthOf(4); child.setAttributeValue('text', undefined as any); @@ -567,7 +567,7 @@ describe('XmlFile', () => { `); }); - it(`honors the 'needsTranspiled' flag when set in 'afterFileParse'`, () => { + it(`honors the 'needsTranspiled' flag when set in 'afterProvideFile'`, () => { program.plugins.add({ name: 'test', afterProvideFile: (event) => { @@ -990,7 +990,7 @@ describe('XmlFile', () => { }); program.plugins.add({ name: 'Transform plugins', - onFileValidate: event => validateXml(event.file as XmlFile) + validateFile: event => validateXml(event.file as XmlFile) }); file = program.setFile('components/component.xml', trim` diff --git a/src/interfaces.ts b/src/interfaces.ts index aa74220b7..8fb04f3b2 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -239,11 +239,12 @@ export interface Plugin { /** * Called before a new program is created */ - beforeProgramCreate?(event: BeforeProgramCreateEvent): any; + beforeProvideProgram?(event: BeforeProvideProgramEvent): any; + provideProgram?(event: ProvideProgramEvent): any; /** * Called after a new program is created */ - afterProgramCreate?(event: AfterProgramCreateEvent): any; + afterProvideProgram?(event: AfterProvideProgramEvent): any; /** @@ -263,20 +264,22 @@ export interface Plugin { /** * Called before the entire program is validated */ - beforeProgramValidate?(event: BeforeProgramValidateEvent): any; + beforeValidateProgram?(event: BeforeValidateProgramEvent): any; /** * Called before the entire program is validated */ - onProgramValidate?(event: OnProgramValidateEvent): any; + validateProgram?(event: ValidateProgramEvent): any; /** * Called after the program has been validated */ - afterProgramValidate?(event: AfterProgramValidateEvent): any; + afterValidateProgram?(event: AfterValidateProgramEvent): any; /** * Called right before the program is disposed/destroyed */ - beforeProgramDispose?(event: BeforeProgramDisposeEvent): any; + beforeRemoveProgram?(event: BeforeRemoveProgramEvent): any; + removeProgram?(event: RemoveProgramEvent): any; + afterRemoveProgram?(event: AfterRemoveProgramEvent): any; /** * Emitted before the program starts collecting completions @@ -308,13 +311,14 @@ export interface Plugin { /** * Called after a scope was created */ - afterScopeCreate?(event: AfterScopeCreateEvent): any; + beforeProvideScope?(event: BeforeProvideScopeEvent): any; + provideScope?(event: ProvideScopeEvent): any; + afterProvideScope?(event: AfterProvideScopeEvent): any; - beforeScopeDispose?(event: BeforeScopeDisposeEvent): any; - onScopeDispose?(event: OnScopeDisposeEvent): any; - afterScopeDispose?(event: AfterScopeDisposeEvent): any; + beforeRemoveScope?(event: BeforeRemoveProgramEvent): any; + removeScope?(event: RemoveScopeEvent): any; + afterRemoveScope?(event: AfterRemoveScopeEvent): any; - beforeScopeValidate?(event: BeforeScopeValidateEvent): any; /** * Called before the `provideDefinition` hook */ @@ -378,11 +382,17 @@ export interface Plugin { afterProvideWorkspaceSymbols?(event: AfterProvideWorkspaceSymbolsEvent): any; //scope events - onScopeValidate?(event: OnScopeValidateEvent): any; - afterScopeValidate?(event: BeforeScopeValidateEvent): any; + beforeValidateScope?(event: BeforeValidateScopeEvent): any; + validateScope?(event: ValidateScopeEvent): any; + afterValidateScope?(event: AfterValidateScopeEvent): any; - onGetCodeActions?(event: OnGetCodeActionsEvent): any; - onGetSemanticTokens?(event: OnGetSemanticTokensEvent): any; + beforeProvideCodeActions?(event: BeforeProvideCodeActionsEvent): any; + provideCodeActions?(event: ProvideCodeActionsEvent): any; + afterProvideCodeActions?(event: AfterProvideCodeActionsEvent): any; + + beforeProvideSemanticTokens?(event: BeforeProvideSemanticTokensEvent): any; + provideSemanticTokens?(event: ProvideSemanticTokensEvent): any; + afterProvideSemanticTokens?(event: AfterProvideSemanticTokensEvent): any; /** @@ -404,35 +414,35 @@ export interface Plugin { * Called before a file is added to the program. * Includes physical files as well as any virtual files produced by `provideFile` events */ - beforeFileAdd?(event: BeforeFileAddEvent): any; + beforeAddFile?(event: BeforeAddFileEvent): any; /** * Called after a file has been added to the program. * Includes physical files as well as any virtual files produced by `provideFile` events */ - afterFileAdd?(event: AfterFileAddEvent): any; + afterAddFile?(event: AfterAddFileEvent): any; /** * Called before a file is removed from the program. This includes physical and virtual files */ - beforeFileRemove?(event: BeforeFileRemoveEvent): any; + beforeRemoveFile?(event: BeforeRemoveFileEvent): any; /** * Called after a file has been removed from the program. This includes physical and virtual files */ - afterFileRemove?(event: AfterFileRemoveEvent): any; + afterRemoveFile?(event: AfterRemoveFileEvent): any; /** * Called before each file is validated */ - beforeFileValidate?(event: BeforeFileValidateEvent): any; + beforeValidateFile?(event: BeforeValidateFileEvent): any; /** * Called during the file validation process. If your plugin contributes file validations, this is a good place to contribute them. */ - onFileValidate?(event: OnFileValidateEvent): any; + validateFile?(event: ValidateFileEvent): any; /** * Called after each file is validated */ - afterFileValidate?(event: AfterFileValidateEvent): any; + afterValidateFile?(event: AfterValidateFileEvent): any; /** @@ -466,7 +476,7 @@ export interface Plugin { /** * Emitted right at the start of the program turning all file objects into their final buffers */ - onSerializeProgram?(event: OnSerializeProgramEvent): any; + serializeProgram?(event: SerializeProgramEvent): any; /** * After the program turns all file objects into their final buffers */ @@ -511,8 +521,23 @@ export interface Plugin { */ afterWriteFile?(event: AfterWriteFileEvent): any; } - -export interface OnGetCodeActionsEvent { +export interface BeforeProvideCodeActionsEvent { + program: Program; + file: TFile; + range: Range; + scopes: Scope[]; + diagnostics: BsDiagnostic[]; + codeActions: CodeAction[]; +} +export interface ProvideCodeActionsEvent { + program: Program; + file: TFile; + range: Range; + scopes: Scope[]; + diagnostics: BsDiagnostic[]; + codeActions: CodeAction[]; +} +export interface AfterProvideCodeActionsEvent { program: Program; file: TFile; range: Range; @@ -521,19 +546,24 @@ export interface OnGetCodeActionsEvent { codeActions: CodeAction[]; } -export interface BeforeProgramCreateEvent { +export interface BeforeProvideProgramEvent { builder: ProgramBuilder; + program?: Program; } -export interface AfterProgramCreateEvent { +export interface ProvideProgramEvent { + builder: ProgramBuilder; + program?: Program; +} +export interface AfterProvideProgramEvent { builder: ProgramBuilder; program: Program; } -export interface BeforeProgramValidateEvent { +export interface BeforeValidateProgramEvent { program: Program; } -export type OnProgramValidateEvent = BeforeProgramValidateEvent; -export interface AfterProgramValidateEvent extends BeforeProgramValidateEvent { +export type ValidateProgramEvent = BeforeValidateProgramEvent; +export interface AfterValidateProgramEvent extends BeforeValidateProgramEvent { /** * Was the validation cancelled? Will be false if the validation was completed */ @@ -587,27 +617,34 @@ export interface Hover { export type BeforeProvideHoverEvent = ProvideHoverEvent; export type AfterProvideHoverEvent = ProvideHoverEvent; -export interface AfterScopeCreateEvent { +export interface BeforeProvideScopeEvent { + program: Program; + scope: Scope; +} +export interface ProvideScopeEvent { program: Program; scope: Scope; } -export interface BeforeScopeDisposeEvent { +export interface AfterProvideScopeEvent { program: Program; scope: Scope; } -export interface OnScopeDisposeEvent { +export interface BeforeRemoveProgramEvent { + program: Program; +} +export interface RemoveScopeEvent { program: Program; scope: Scope; } -export interface AfterScopeDisposeEvent { +export interface AfterRemoveScopeEvent { program: Program; scope: Scope; } -export interface BeforeScopeValidateEvent { +export interface BeforeValidateScopeEvent { program: Program; scope: Scope; } -export type AfterScopeValidateEvent = BeforeScopeValidateEvent; +export type AfterValidateScopeEvent = BeforeValidateScopeEvent; export interface BeforeFileParseEvent { program: Program; @@ -686,7 +723,43 @@ export type BeforeProvideWorkspaceSymbolsEvent = ProvideWorkspaceSymbolsEvent; export type AfterProvideWorkspaceSymbolsEvent = ProvideWorkspaceSymbolsEvent; -export interface OnGetSemanticTokensEvent { +export interface BeforeProvideSemanticTokensEvent { + /** + * The program this file is from + */ + program: Program; + /** + * The file to get semantic tokens for + */ + file: T; + /** + * The list of scopes that this file is a member of + */ + scopes: Scope[]; + /** + * The list of semantic tokens being produced during this event. + */ + semanticTokens: SemanticToken[]; +} +export interface ProvideSemanticTokensEvent { + /** + * The program this file is from + */ + program: Program; + /** + * The file to get semantic tokens for + */ + file: T; + /** + * The list of scopes that this file is a member of + */ + scopes: Scope[]; + /** + * The list of semantic tokens being produced during this event. + */ + semanticTokens: SemanticToken[]; +} +export interface AfterProvideSemanticTokensEvent { /** * The program this file is from */ @@ -705,14 +778,14 @@ export interface OnGetSemanticTokensEvent { semanticTokens: SemanticToken[]; } -export type BeforeFileValidateEvent = OnFileValidateEvent; -export interface OnFileValidateEvent { +export type BeforeValidateFileEvent = ValidateFileEvent; +export interface ValidateFileEvent { program: Program; file: T; } -export type AfterFileValidateEvent = OnFileValidateEvent; +export type AfterValidateFileEvent = ValidateFileEvent; -export interface OnFileValidateEvent { +export interface ValidateFileEvent { program: Program; file: T; } @@ -730,7 +803,7 @@ export interface ScopeValidationOptions { initialValidation?: boolean; } -export interface OnScopeValidateEvent { +export interface ValidateScopeEvent { program: Program; scope: Scope; changedFiles?: BscFile[]; @@ -795,17 +868,17 @@ export interface ProvideFileEvent { } export type AfterProvideFileEvent = ProvideFileEvent; -export interface BeforeFileAddEvent { +export interface BeforeAddFileEvent { file: TFile; program: Program; } -export type AfterFileAddEvent = BeforeFileAddEvent; +export type AfterAddFileEvent = BeforeAddFileEvent; -export interface BeforeFileRemoveEvent { +export interface BeforeRemoveFileEvent { file: TFile; program: Program; } -export type AfterFileRemoveEvent = BeforeFileRemoveEvent; +export type AfterRemoveFileEvent = BeforeRemoveFileEvent; export type BeforePrepareProgramEvent = PrepareProgramEvent; /** @@ -852,7 +925,7 @@ export interface BeforeSerializeProgramEvent { files: BscFile[]; result: Map; } -export type OnSerializeProgramEvent = BeforeSerializeProgramEvent; +export type SerializeProgramEvent = BeforeSerializeProgramEvent; export type AfterSerializeProgramEvent = BeforeSerializeProgramEvent; /** @@ -924,10 +997,15 @@ export interface BeforeFileDisposeEvent { file: BscFile; } export type AfterFileDisposeEvent = BeforeFileDisposeEvent; -export interface BeforeProgramDisposeEvent { +export interface BeforeRemoveProgramEvent { + program: Program; +} +export interface RemoveProgramEvent { + program: Program; +} +export interface AfterRemoveProgramEvent { program: Program; } - export interface SemanticToken { range: Range; tokenType: SemanticTokenTypes; diff --git a/src/lsp/Project.spec.ts b/src/lsp/Project.spec.ts index 8b5bfceaa..d5b2ea6b7 100644 --- a/src/lsp/Project.spec.ts +++ b/src/lsp/Project.spec.ts @@ -70,11 +70,11 @@ describe('Project', () => { project['builder'].program.plugins.add({ name: 'Test', - beforeProgramValidate: () => { + beforeValidateProgram: () => { validationCount++; maxValidationCount = Math.max(maxValidationCount, validationCount); }, - afterProgramValidate: () => { + afterValidateProgram: () => { validationCount--; } }); diff --git a/src/lsp/Project.ts b/src/lsp/Project.ts index 245a20f49..e0167ca98 100644 --- a/src/lsp/Project.ts +++ b/src/lsp/Project.ts @@ -93,7 +93,7 @@ export class Project implements LspProject { //this plugin must be added LAST to the program to ensure we can see all diagnostics this.builder.plugins.add({ name: 'bsc-language-server', - afterProgramValidate: () => { + afterValidateProgram: () => { const diagnostics = this.getDiagnostics(); this.emit('diagnostics', { diagnostics: diagnostics diff --git a/src/lsp/ProjectManager.spec.ts b/src/lsp/ProjectManager.spec.ts index cdac60c2e..5456aaddb 100644 --- a/src/lsp/ProjectManager.spec.ts +++ b/src/lsp/ProjectManager.spec.ts @@ -217,11 +217,11 @@ describe('ProjectManager', () => { ]); }); - it('gets diagnostics from plugins added in afterProgramValidate', async () => { + it('gets diagnostics from plugins added in afterValidateProgram', async () => { fsExtra.outputFileSync(`${rootDir}/plugin.js`, ` module.exports = function () { return { - afterProgramValidate: function(event) { + afterValidateProgram: function(event) { var file = event.program.getFile('source/main.brs'); //add a diagnostic from a plugin event.program.diagnostics.register({ @@ -1059,7 +1059,7 @@ describe('ProjectManager', () => { this.server.listen(port, host); } - afterProgramCreate(program: Program) { + afterProvideProgram(program: Program) { // hijack the function to get workspace symbols, return a promise that resolves in the future program.getWorkspaceSymbols = () => { return this.deferred.promise as any; diff --git a/src/parser/AstNode.ts b/src/parser/AstNode.ts index 34cb2a970..d5f5ddee3 100644 --- a/src/parser/AstNode.ts +++ b/src/parser/AstNode.ts @@ -45,7 +45,7 @@ export abstract class AstNode { public abstract walk(visitor: WalkVisitor, options: WalkOptions); /** - * The parent node for this statement. This is set dynamically during `onFileValidate`, and should not be set directly. + * The parent node for this statement. This is set dynamically during `validateFile`, and should not be set directly. */ public parent?: AstNode; diff --git a/src/util.spec.ts b/src/util.spec.ts index cc73bab8f..6c8851a87 100644 --- a/src/util.spec.ts +++ b/src/util.spec.ts @@ -1738,6 +1738,31 @@ describe('util', () => { }); }); + + describe('virtual:/ paths', () => { + it('preserves virtual:/ prefix on windows', () => { + isWindows = true; + test('virtual:/ButtonPrimary.brs', 'virtual:/buttonprimary.brs'); + test('virtual:\\ButtonPrimary.brs', 'virtual:/buttonprimary.brs'); + }); + + it('preserves virtual:/ prefix on unix', () => { + isWindows = false; + test('virtual:/ButtonPrimary.brs', 'virtual:/buttonprimary.brs'); + test('virtual:\\ButtonPrimary.brs', 'virtual:/buttonprimary.brs'); + }); + + it('normalizes consecutive slashes in virtual paths', () => { + isWindows = true; + test('virtual://one//two.brs', 'virtual:/one/two.brs'); + isWindows = false; + test('virtual://one//two.brs', 'virtual:/one/two.brs'); + }); + + it('lowercases virtual path content', () => { + test('virtual:/UPPER/CasePath.brs', 'virtual:/upper/casepath.brs'); + }); + }); }); describe('isClassUsedAsFunction', () => {