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

rokucommunity / brighterscript / #13307

22 Nov 2024 02:25PM UTC coverage: 86.801%. Remained the same
#13307

push

web-flow
Merge 332332a1f into 2a6afd921

11833 of 14419 branches covered (82.07%)

Branch coverage included in aggregate %.

191 of 205 new or added lines in 26 files covered. (93.17%)

201 existing lines in 18 files now uncovered.

12868 of 14038 relevant lines covered (91.67%)

32019.9 hits per line

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

77.59
/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,810✔
33
        this.suppressErrors = (options as any)?.suppressErrors === false ? false : true;
1,810!
34
        for (const plugin of plugins ?? []) {
1,810✔
35
            this.add(plugin);
×
36
        }
37
        if (!this.logger) {
1,810✔
38
            this.logger = createLogger();
1✔
39
        }
40
        if (!this.logger) {
1,810!
41
            this.logger = createLogger();
×
42
        }
43
    }
44

45
    private plugins: CompilerPlugin[] = [];
1,810✔
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
        this.logger.debug(`Emitting plugin event: ${event}`);
37,334✔
58
        for (let plugin of this.plugins) {
37,334✔
59
            if ((plugin as any)[event]) {
38,834✔
60
                try {
11,556✔
61
                    this.logger?.time(LogLevel.debug, [plugin.name, event], () => {
11,556!
62
                        (plugin as any)[event](...args);
11,556✔
63
                    });
64
                } catch (err) {
65
                    this.logger?.error(`Error when calling plugin ${plugin.name}.${event}:`, (err as Error).stack);
4!
66
                    if (!this.suppressErrors) {
4!
UNCOV
67
                        throw err;
×
68
                    }
69
                }
70
            }
71
        }
72
        return args[0];
37,334✔
73
    }
74

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

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

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

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

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

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

157
    public has(plugin: CompilerPlugin) {
158
        return this.plugins.includes(plugin);
2,486✔
159
    }
160

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

168
    /**
169
     * Remove all plugins
170
     */
171
    public clear() {
UNCOV
172
        this.plugins = [];
×
173
    }
174
}
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