From bfffe479f8603ffd5ad6a2268fbfe3b54689aa53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 8 Apr 2019 13:37:13 +0200 Subject: [PATCH] feat(build): add more TypeScript "strict" checks Enable all "strict" checks with the exception of `strictPropertyInitialization` which would require significant changes. The following additional checks are added: - noImplicitThis - alwaysStrict - strictFunctionTypes In the future, any checks added to TypeScript "strict" mode will be automatically enabled for LoopBack projects too. --- benchmark/src/benchmark.ts | 9 ++++-- packages/build/config/tsconfig.common.json | 7 ++-- packages/context/src/context-view.ts | 32 +++++++++---------- packages/core/src/lifecycle.ts | 2 +- .../src/__tests__/unit/type-resolver.unit.ts | 10 ++++-- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/benchmark/src/benchmark.ts b/benchmark/src/benchmark.ts index efebf07fe0ed..cd1ea48df420 100644 --- a/benchmark/src/benchmark.ts +++ b/benchmark/src/benchmark.ts @@ -5,7 +5,7 @@ import * as byline from 'byline'; import {ChildProcess, spawn} from 'child_process'; -import pEvent from 'p-event'; +import pEvent, {Emitter} from 'p-event'; import {Autocannon, EndpointStats} from './autocannon'; import {Client} from './client'; import {scenarios} from './scenarios'; @@ -111,5 +111,10 @@ function startWorker() { async function closeWorker(worker: ChildProcess) { worker.kill(); - await pEvent(worker, 'close'); + await pEvent( + // workaround for a bug in pEvent types which makes them + // incompatible with "strictFunctionTypes" + worker as Emitter<[unknown]>, + 'close', + ); } diff --git a/packages/build/config/tsconfig.common.json b/packages/build/config/tsconfig.common.json index eb58cee2e096..5aa85c2b0b3b 100644 --- a/packages/build/config/tsconfig.common.json +++ b/packages/build/config/tsconfig.common.json @@ -3,11 +3,12 @@ "compilerOptions": { "emitDecoratorMetadata": true, "experimentalDecorators": true, - "noImplicitAny": true, - "strictNullChecks": true, "resolveJsonModule": true, - "strictBindCallApply": true, "skipLibCheck": true, + "strict": true, + + // FIXME(bajtos) LB4 is not compatible with this setting yet + "strictPropertyInitialization": false, "lib": ["es2018", "esnext.asynciterable"], "module": "commonjs", diff --git a/packages/context/src/context-view.ts b/packages/context/src/context-view.ts index a354cf44aecc..8bb3ebfc8de1 100644 --- a/packages/context/src/context-view.ts +++ b/packages/context/src/context-view.ts @@ -4,19 +4,15 @@ // License text available at https://opensource.org/licenses/MIT import * as debugFactory from 'debug'; -import {EventEmitter} from 'events'; -import {promisify} from 'util'; -import {Binding} from './binding'; -import {BindingFilter} from './binding-filter'; -import {Context} from './context'; -import { - ContextEventType, - ContextObserver, - Subscription, -} from './context-observer'; -import {Getter} from './inject'; -import {ResolutionSession} from './resolution-session'; -import {isPromiseLike, resolveList, ValueOrPromise} from './value-promise'; +import { EventEmitter } from 'events'; +import { promisify } from 'util'; +import { Binding } from './binding'; +import { BindingFilter } from './binding-filter'; +import { Context } from './context'; +import { ContextEventType, ContextObserver, Subscription } from './context-observer'; +import { Getter } from './inject'; +import { ResolutionSession } from './resolution-session'; +import { isPromiseLike, resolveList, ValueOrPromise } from './value-promise'; const debug = debugFactory('loopback:context:view'); const nextTick = promisify(process.nextTick); @@ -45,6 +41,9 @@ export class ContextView extends EventEmitter public readonly filter: BindingFilter, ) { super(); + + // Workaround to pass TypeScript's "strictFunctionTypes" check + this.filter = filter as BindingFilter; } /** @@ -85,10 +84,11 @@ export class ContextView extends EventEmitter /** * Find matching bindings and refresh the cache */ - protected findBindings() { + protected findBindings(): Readonly>[] { debug('Finding matching bindings'); - this._cachedBindings = this.context.find(this.filter); - return this._cachedBindings; + const found = this.context.find(this.filter); + this._cachedBindings = found; + return found; } /** diff --git a/packages/core/src/lifecycle.ts b/packages/core/src/lifecycle.ts index 606473d4bc89..8b59b8f008b4 100644 --- a/packages/core/src/lifecycle.ts +++ b/packages/core/src/lifecycle.ts @@ -62,7 +62,7 @@ export function asLifeCycleObserver(binding: Binding) { * Find all life cycle observer bindings. By default, a binding tagged with * `CoreTags.LIFE_CYCLE_OBSERVER`. It's used as `BindingFilter`. */ -export function lifeCycleObserverFilter(binding: Binding): boolean { +export function lifeCycleObserverFilter(binding: Readonly): boolean { return binding.tagMap[CoreTags.LIFE_CYCLE_OBSERVER] != null; } diff --git a/packages/repository/src/__tests__/unit/type-resolver.unit.ts b/packages/repository/src/__tests__/unit/type-resolver.unit.ts index ceb469741bd1..a3079a4c7842 100644 --- a/packages/repository/src/__tests__/unit/type-resolver.unit.ts +++ b/packages/repository/src/__tests__/unit/type-resolver.unit.ts @@ -4,7 +4,12 @@ // License text available at https://opensource.org/licenses/MIT import {expect} from '@loopback/testlab'; -import {isBuiltinType, isTypeResolver, resolveType} from '../../type-resolver'; +import { + isBuiltinType, + isTypeResolver, + resolveType, + TypeResolver, +} from '../../type-resolver'; class SomeModel { constructor(public name: string) {} @@ -60,7 +65,8 @@ describe('isTypeResolver', () => { describe('resolveType', () => { it('resolves the arg when the value is a resolver', () => { - const ctor = resolveType(() => SomeModel); + const resolver: TypeResolver = () => SomeModel; + const ctor = resolveType(resolver); expect(ctor).to.eql(SomeModel); const inst = new ctor('a-name');