• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / brighterscript / #12931

13 Aug 2024 05:02PM UTC coverage: 86.193% (-1.7%) from 87.933%
#12931

push

web-flow
Merge 58ad447a2 into 0e968f1c3

10630 of 13125 branches covered (80.99%)

Branch coverage included in aggregate %.

6675 of 7284 new or added lines in 99 files covered. (91.64%)

84 existing lines in 18 files now uncovered.

12312 of 13492 relevant lines covered (91.25%)

26867.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.93
/src/PluginInterface.ts
1
import type { CompilerPlugin } from './interfaces';
2
import { LogLevel, createLogger, type Logger } from './logging';
1✔
3
/*
4
 * we use `Required` everywhere here because we expect that the methods on plugin objects will
5
 * be optional, and we don't want to deal with `undefined`.
6
 * `extends (...args: any[]) => any` determines whether the thing we're dealing with is a function.
7
 * Returning `never` in the `as` clause of the `[key in object]` step deletes that key from the
8
 * resultant object.
9
 * on the right-hand side of the mapped type we are forced to use a conditional type a second time,
10
 * in order to be able to use the `Parameters` utility type on `Required<T>[K]`. This will always
11
 * be true because of the filtering done by the `[key in object]` clause, but TS requires the duplication.
12
 *
13
 * so put together: we iterate over all of the fields in T, deleting ones which are not (potentially
14
 * optional) functions. For the ones that are, we replace them with their parameters.
15
 *
16
 * this returns the type of an object whose keys are the names of the methods of T and whose values
17
 * are tuples containing the arguments that each method accepts.
18
 */
19
export type PluginEventArgs<T> = {
20
    [K in keyof Required<T> as Required<T>[K] extends (...args: any[]) => any ? K : never]:
21
    Required<T>[K] extends (...args: any[]) => any ? Parameters<Required<T>[K]> : never
22
};
23

24
export default class PluginInterface<T extends CompilerPlugin = CompilerPlugin> {
1✔
25
    constructor(
26
        plugins?: CompilerPlugin[],
27
        options?: {
28
            logger?: Logger;
29
            suppressErrors?: boolean;
30
        }
31
    ) {
32
        this.logger = options?.logger;
1,571✔
33
        this.suppressErrors = (options as any)?.suppressErrors === false ? false : true;
1,571!
34
        for (const plugin of plugins ?? []) {
1,571✔
NEW
35
            this.add(plugin);
×
36
        }
37
        if (!this.logger) {
1,571✔
38
            this.logger = createLogger();
1✔
39
        }
40
        if (!this.logger) {
1,571!
UNCOV
41
            this.logger = createLogger();
×
42
        }
43
    }
44

45
    private plugins: CompilerPlugin[] = [];
1,571✔
46
    private logger: Logger;
47

48
    /**
49
     * Should plugin errors cause the program to fail, or should they be caught and simply logged
50
     */
51
    private suppressErrors: boolean | undefined;
52

53
    /**
54
     * Call `event` on plugins
55
     */
56
    public emit<K extends keyof PluginEventArgs<T> & string>(event: K, ...args: PluginEventArgs<T>[K]) {
57
        for (let plugin of this.plugins) {
34,378✔
58
            if ((plugin as any)[event]) {
35,849✔
59
                try {
10,691✔
60
                    this.logger?.time(LogLevel.debug, [plugin.name, event], () => {
10,691!
61
                        (plugin as any)[event](...args);
10,691✔
62
                    });
63
                } catch (err) {
NEW
64
                    this.logger?.error(`Error when calling plugin ${plugin.name}.${event}:`, (err as Error).stack);
×
UNCOV
65
                    if (!this.suppressErrors) {
×
66
                        throw err;
×
67
                    }
68
                }
69
            }
70
        }
71
        return args[0];
34,378✔
72
    }
73

74
    /**
75
     * Call `event` on plugins, but allow the plugins to return promises that will be awaited before the next plugin is notified
76
     */
77
    public async emitAsync<K extends keyof PluginEventArgs<T> & string>(event: K, ...args: PluginEventArgs<T>[K]): Promise< PluginEventArgs<T>[K][0]> {
78
        for (let plugin of this.plugins) {
9,678✔
79
            if ((plugin as any)[event]) {
18,432✔
80
                try {
3,470✔
81
                    await this.logger?.time(LogLevel.debug, [plugin.name, event], async () => {
3,470!
82
                        await Promise.resolve(
3,470✔
83
                            (plugin as any)[event](...args)
84
                        );
85
                    });
86
                } catch (err) {
87
                    this.logger?.error(`Error when calling plugin ${plugin.name}.${event}:`, err);
2!
88
                }
89
            }
90
        }
91
        return args[0];
9,678✔
92
    }
93

94
    /**
95
     * Add a plugin to the beginning of the list of plugins
96
     */
97
    public addFirst<T extends CompilerPlugin = CompilerPlugin>(plugin: T) {
98
        if (!this.has(plugin)) {
1,833!
99
            this.plugins.unshift(plugin);
1,833✔
100
        }
101
        return plugin;
1,833✔
102
    }
103

104
    /**
105
     * Add a plugin to the end of the list of plugins
106
     */
107
    public add<T extends CompilerPlugin = CompilerPlugin>(plugin: T) {
108
        if (!this.has(plugin)) {
78✔
109
            this.sanitizePlugin(plugin);
77✔
110
            this.plugins.push(plugin);
77✔
111
        }
112
        return plugin;
78✔
113
    }
114

115
    /**
116
     * Find deprecated or removed historic plugin hooks, and warn about them.
117
     * Some events can be forwards-converted
118
     */
119
    private sanitizePlugin(plugin: CompilerPlugin) {
120
        const removedHooks = [
77✔
121
            'beforePrepublish',
122
            'afterPrepublish'
123
        ];
124
        for (const removedHook of removedHooks) {
77✔
125
            if (plugin[removedHook]) {
154!
NEW
126
                this.logger?.error(`Plugin "${plugin.name}": event ${removedHook} is no longer supported and will never be called`);
×
127
            }
128
        }
129

130
        const upgradeWithWarn = {
77✔
131
            beforePublish: 'beforeSerializeProgram',
132
            afterPublish: 'afterSerializeProgram',
133
            beforeProgramTranspile: 'beforeBuildProgram',
134
            afterProgramTranspile: 'afterBuildProgram',
135
            beforeFileParse: 'beforeProvideFile',
136
            afterFileParse: 'afterProvideFile',
137
            beforeFileTranspile: 'beforePrepareFile',
138
            afterFileTranspile: 'afterPrepareFile',
139
            beforeFileDispose: 'beforeFileRemove',
140
            afterFileDispose: 'afterFileRemove'
141
        };
142

143
        for (const [oldEvent, newEvent] of Object.entries(upgradeWithWarn)) {
77✔
144
            if (plugin[oldEvent]) {
770✔
145
                if (!plugin[newEvent]) {
7!
146
                    plugin[newEvent] = plugin[oldEvent];
7✔
147
                    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.`);
7!
148
                } else {
NEW
149
                    this.logger?.warn(`Plugin "${plugin.name}": event '${oldEvent}' is no longer supported and will never be called`);
×
150
                }
151
            }
152
        }
153
    }
154

155
    public has(plugin: CompilerPlugin) {
156
        return this.plugins.includes(plugin);
2,197✔
157
    }
158

159
    public remove<T extends CompilerPlugin = CompilerPlugin>(plugin: T) {
160
        if (this.has(plugin)) {
283!
161
            this.plugins.splice(this.plugins.indexOf(plugin), 1);
283✔
162
        }
163
        return plugin;
283✔
164
    }
165

166
    /**
167
     * Remove all plugins
168
     */
169
    public clear() {
170
        this.plugins = [];
×
171
    }
172
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc