From c86387ea09c72902380b413a3a5bd3fa80e416f8 Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Thu, 26 Mar 2020 13:36:41 +0100 Subject: [PATCH] [core] added typescript decorators (experimental) --- packages/core/src/api/app/FeatureFlags.tsx | 20 ++++++---- packages/core/src/testUtils/decorators.ts | 45 ++++++++++++++++++++++ packages/core/src/testUtils/index.js | 1 + packages/core/tsconfig.json | 3 +- 4 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 packages/core/src/testUtils/decorators.ts diff --git a/packages/core/src/api/app/FeatureFlags.tsx b/packages/core/src/api/app/FeatureFlags.tsx index fc0d25dee5..e55660f240 100644 --- a/packages/core/src/api/app/FeatureFlags.tsx +++ b/packages/core/src/api/app/FeatureFlags.tsx @@ -17,13 +17,15 @@ // import React, { createContext, useContext, useState, FC } from 'react'; import { FeatureFlagName } from '../plugin/types'; import { FeatureFlagsApi } from '../apis/definitions/featureFlags'; +import { staticImplements } from '../../testUtils'; // TODO: figure out where to put implementations of APIs, both inside apps // but also in core/separate package. -export class FeatureFlags implements FeatureFlagsApi { +@staticImplements() +export class FeatureFlags { private static readonly localStorageKey = 'featureFlags'; - private static getEnabledFeatureFlags(): Set { + private static getUserEnabledFeatureFlags(): Set { if (!('localStorage' in window)) { throw new Error( 'Feature Flags are not supported on browsers without the Local Storage API', @@ -40,7 +42,9 @@ export class FeatureFlags implements FeatureFlagsApi { } } - private static saveFeatureFlags(flags: Set): void { + private static saveUserEnabledFeatureFlags( + flags: Set, + ): void { if (!('localStorage' in window)) { throw new Error( 'Feature Flags are not supported on browsers without the Local Storage API', @@ -56,18 +60,18 @@ export class FeatureFlags implements FeatureFlagsApi { } static getItem(name: FeatureFlagName): boolean { - return this.getFeatureFlags().has(name); + return this.getUserEnabledFeatureFlags().has(name); } static enable(name: FeatureFlagName): void { - const flags = this.getFeatureFlags(); + const flags = this.getUserEnabledFeatureFlags(); flags.add(name); - this.saveFeatureFlags(flags); + this.saveUserEnabledFeatureFlags(flags); } static disable(name: FeatureFlagName): void { - const flags = this.getFeatureFlags(); + const flags = this.getUserEnabledFeatureFlags(); flags.delete(name); - this.saveFeatureFlags(flags); + this.saveUserEnabledFeatureFlags(flags); } } diff --git a/packages/core/src/testUtils/decorators.ts b/packages/core/src/testUtils/decorators.ts new file mode 100644 index 0000000000..2cea8901b4 --- /dev/null +++ b/packages/core/src/testUtils/decorators.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This utilizes an experimental TypeScript feature called decorators. + * This was originally added to allow us to expose a FeatureFlags API + * with static methods (for backwards compatibility primarily). It takes + * an existing interface and applies the types to static methods in classes. + * + * @see https://www.typescriptlang.org/docs/handbook/decorators.html + * @example + * interface StaticProps { + * append(name: string, extra: string): string; + * reverse(name: string): string; + * } + * + * @staticImplements() + * class StaticPropsClass { + * static append(name, extra) { + * return `${name}${extra}`; + * } + * + * static reverse(name) { + * return name.reverse(); + * } + * } + */ +export function staticImplements() { + return (constructor: U) => { + constructor; + }; +} diff --git a/packages/core/src/testUtils/index.js b/packages/core/src/testUtils/index.js index a0eab3e72b..902ba5d92d 100644 --- a/packages/core/src/testUtils/index.js +++ b/packages/core/src/testUtils/index.js @@ -28,6 +28,7 @@ import { render } from '@testing-library/react'; export { default as Keyboard } from './Keyboard'; export { default as mockBreakpoint } from './mockBreakpoint'; export * from './logCollector'; +export * from './decorators'; export function wrapInTestApp(Component, initialRouterEntries) { const Wrapper = Component instanceof Function ? Component : () => Component; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index ad43a0b986..4e024e548f 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.json", "include": ["src"], "compilerOptions": { - "noImplicitAny": false + "noImplicitAny": false, + "experimentalDecorators": true } }