blob: 77d1b57c128a5eedee2f622965553be85ee5a479 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ## How to use feature flags:
//
// - define a feature flag in `src/all-feature-flags.js`.
// Check a few of the testing flags for examples
//
// - run `npm build`, which will *autogenerate* package.json configuration
// for your flags. You *DO NOT* edit package.json manually for this.
//
// - call `#enabled`:
//
// ```
// import * as features from './features';
//
// if (features.enabled('myFeature')) { ... }
// ```
//
// Typescript will ensure you're only using defined flags
//
// ## How to name feature flags
//
// - use `camelCase` (this matches the vscode settings conventions)
// - use words that'd look good as a title, because they'll show up
// like that in the vscode settings page.
//
// For example, `showDevicePaneFromStatusBar` will become
// `Show Device Pane From Status Bar` to users on the setting page.
//
// - avoid "new" and words like that (unless referencing an external
// thing) -- anything new will be old one day, and then you'll
// be stuck with `newNew` and then `newNewNew`. Prefer versions
// (e.g. `devicePicker2`) or just clear descriptions when possible.
import * as vscode from 'vscode';
import * as flags from './all-feature-flags';
/// the current base stability level -- will be autodetected
/// based on version number & config on #init, but can be overriden
/// with the `fuchsia.featurePreview` flag (can change at runtime).
let stabilityLevel: flags.Stability = 'stable';
/// the current default stability, as detected by the extension version
let defaultStability: flags.Stability = 'stable';
let watcher: vscode.Disposable;
/**
* represents a change to one of the feature flags in this plugin
*/
export class FeatureFlagChangeEvent {
private global;
constructor(private original: vscode.ConfigurationChangeEvent, global?: boolean) {
this.global = global ?? false;
}
/**
* check if this change affects the given flag.
*
* It should never have false negatives, but it may have false positive in cases
* of large-scale config changes.
*/
affectsFlag(name: string): boolean {
if (this.global) {
// it'd be complicated to figure out which flags would've flipped, so prob fine to just tell everyone
// that they're affected
return true;
}
return this.original.affectsConfiguration(`fuchsia.features.${name}`);
}
}
const onDidChangeFeatureFlagEmitter: vscode.EventEmitter<FeatureFlagChangeEvent>
= new vscode.EventEmitter();
/**
* Event emitted when a feature flag changes value.
*
* If you need to activate or deactivate features based on flags, listen to this.
*/
export const onDidChangeFeatureFlag: vscode.Event<FeatureFlagChangeEvent>
= onDidChangeFeatureFlagEmitter.event;
/**
* activates the feature flag system, assuming the given extension version.
*
* You can get the extension version with `ctx.extension.packageJSON.version`.
*/
export function activate(extensionVersion: string) {
const [_major, minor, _patch] = extensionVersion.split('.');
const isPrerelease = parseInt(minor) % 2 === 1; // prerelease versions are always odd
defaultStability = isPrerelease ? 'nightly' : 'stable';
setStabilityFromConfig();
watcher = vscode.workspace.onDidChangeConfiguration((evt) => {
if (evt.affectsConfiguration('fuchsia.featurePreview')) {
setStabilityFromConfig();
onDidChangeFeatureFlagEmitter.fire(new FeatureFlagChangeEvent(evt, true));
return;
}
if (evt.affectsConfiguration('fuchsia.features')) {
onDidChangeFeatureFlagEmitter.fire(new FeatureFlagChangeEvent(evt));
}
});
}
/**
* deactives the feature flag system
*/
export function deactivate() {
if (watcher) {
watcher.dispose();
}
}
/**
* resets #stabilityLevel from either the #defaultStability or the override configuration
* (fuchsia.featurePreview)
*/
function setStabilityFromConfig() {
const override = vscode.workspace.getConfiguration('fuchsia').get('featurePreview');
if (override === 'auto') {
stabilityLevel = defaultStability;
} else {
stabilityLevel = override as flags.Stability;
}
}
/** checks if lhs >= rhs, in stability terms */
function isAtLeast(lhs: flags.Stability, rhs: flags.Stability): boolean {
const levels = {
'never': -1,
'nightly': 0,
'stable': 1,
};
return levels[lhs] >= levels[rhs];
}
/**
* Check if the given flag with with the given stability level would be enabled
* in the current environment, either by configuration override or current
* global stability level.
*
* You should pass in a string, like `flags.enabled('myFlag')`, and typescript's
* typechecking will ensure it's a defined flag.
*/
export function enabled(flagName: flags.Known): boolean {
const opt = vscode.workspace.getConfiguration('fuchsia.features').get(flagName);
switch (opt) {
case 'auto':
const stability = flags.features[flagName].stability;
return isAtLeast(stability, stabilityLevel);
case 'enabled':
return true;
case 'disabled':
return false;
default:
throw new Error(
`feature flag ${flagName} set to invalid value "${opt}" (must be enabled|disabled|auto)`);
}
}
// NB(sollyross): these are in a separate file so that we can load it in
// our build generator and inject the corresponding JSON into package.json
/**
* The list of all feature flags.
*/
export { Stability, features } from './all-feature-flags';