# HG changeset patch # User cin # Date 2020-06-29 04:46:50 # Node ID 691199f665e0e96facb369a2231aa4172c901b9f # Parent 475b8ce3e85004a7bcb45194005139acace13b43 corrected code to support ts strict mode safe.ts - more tight typings - added notImplemented stub function - added fork funtion - added keys function (like Object.keys but extracts keys type) - added isKeyof typeguard - added 'primitive' union type added EventProvider for the observable diff --git a/build.gradle b/build.gradle --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,9 @@ typescript { types = [] declaration = true experimentalDecorators = true - //strict = true + strict = true + // dojo-typings are sick + skipLibCheck = true if(symbols != 'none') { sourceMap = true diff --git a/src/amd/ts/di/ResolverHelper.ts b/src/amd/ts/di/ResolverHelper.ts --- a/src/amd/ts/di/ResolverHelper.ts +++ b/src/amd/ts/di/ResolverHelper.ts @@ -12,7 +12,7 @@ const trace = TraceSource.get(m.id); trace.debug("globalRequire = {0}", globalRequire); class ModuleResolver { - _base: string; + _base: string | undefined; _require: Require; constructor(req: Require, base?: string) { diff --git a/src/amd/ts/log/trace.ts b/src/amd/ts/log/trace.ts --- a/src/amd/ts/log/trace.ts +++ b/src/amd/ts/log/trace.ts @@ -9,7 +9,7 @@ export = { cb = filter; filter = undefined; } - let test: Predicate; + let test: Predicate | undefined; if (filter instanceof RegExp) { test = chId => filter.test(chId); } else if (filter instanceof Function) { @@ -21,7 +21,7 @@ export = { if (test) { TraceSource.on(source => { source.level = this.level; - if (test(source.id)) + if (test && test(source.id)) source.events.on(cb); }); } else { diff --git a/src/amd/ts/text/TemplateCompiler.ts b/src/amd/ts/text/TemplateCompiler.ts --- a/src/amd/ts/text/TemplateCompiler.ts +++ b/src/amd/ts/text/TemplateCompiler.ts @@ -2,6 +2,7 @@ import * as format from "./format"; import { TraceSource, DebugLevel } from "../log/TraceSource"; import { ITemplateParser, TokenType } from "./TemplateParser"; import m = require("module"); +import { isKeyof } from "../safe"; const trace = TraceSource.get(m.id); @@ -21,7 +22,7 @@ const htmlEscaper = /[&<>"'\/]/g; // Escape a string for HTML interpolation. function escapeHtml(string: any) { - return ("" + string).replace(htmlEscaper, match => htmlEscapes[match]); + return ("" + string).replace(htmlEscaper, match => isKeyof(match, htmlEscapes) ? htmlEscapes[match] : ""); } export class TemplateCompiler { diff --git a/src/amd/ts/text/TemplateParser.ts b/src/amd/ts/text/TemplateParser.ts --- a/src/amd/ts/text/TemplateParser.ts +++ b/src/amd/ts/text/TemplateParser.ts @@ -38,7 +38,7 @@ export class TemplateParser implements I _tokens: string[]; _pos = -1; _type: TokenType; - _value: string; + _value: string | undefined; constructor(text: string) { argumentNotEmptyString(text, "text"); @@ -66,6 +66,8 @@ export class TemplateParser implements I } value() { + if (!this._value) + throw new Error("The current token doesn't have a value"); return this._value; } diff --git a/src/amd/ts/text/format.ts b/src/amd/ts/text/format.ts --- a/src/amd/ts/text/format.ts +++ b/src/amd/ts/text/format.ts @@ -2,29 +2,32 @@ import { format as dojoFormatNumber } fr import { format as dojoFormatDate } from "dojo/date/locale"; import { Formatter, compile as _compile } from "./StringFormat"; -import { isNumber, isNull } from "../safe"; +import { isNumber, isNull, get } from "../safe"; interface NumberFormatOptions { round?: number; pattern?: string; } -function convertNumber(value: any, pattern: string) { +function convertNumber(value: any, _pattern?: string) { if (isNumber(value)) { const nopt = {} as NumberFormatOptions; - if (pattern.indexOf("!") === 0) { + let pattern = _pattern; + if (pattern && pattern.indexOf("!") === 0) { nopt.round = -1; pattern = pattern.substr(1); } nopt.pattern = pattern; return dojoFormatNumber(value, nopt); + } else { + return ""; } } -function convertDate(value: any, pattern: string) { +function convertDate(value: any, pattern?: string) { if (value instanceof Date) { - const m = pattern.match(/^(\w+)-(\w+)$/); + const m = pattern && pattern.match(/^(\w+)-(\w+)$/); if (m) return dojoFormatDate(value, { selector: m[2], @@ -37,6 +40,8 @@ function convertDate(value: any, pattern selector: "date", datePattern: pattern }); + } else { + return ""; } } @@ -46,7 +51,7 @@ function format(msg: string, ...args: an return _formatter.format(msg, ...args); } -function _convert(value: any, pattern: string) { +function _convert(value: any, pattern?: string) { return _formatter.convert(value, pattern); } @@ -55,9 +60,9 @@ namespace format { export function compile(text: string) { const template = _compile(text); - return (...data) => { + return (...data: any[]) => { return template((name, pattern) => { - const value = data[name]; + const value = get(name, data); return !isNull(value) ? convert(value, pattern) : ""; }); }; diff --git a/src/amd/ts/text/template-compile.ts b/src/amd/ts/text/template-compile.ts --- a/src/amd/ts/text/template-compile.ts +++ b/src/amd/ts/text/template-compile.ts @@ -34,7 +34,7 @@ compile.load = (id: string, require: Req callback(cache[url]); } else { trace.debug("{0} -> {1}: load", id, url); - request(url).then(compile).then((tc: TemplateFn) => { + request(url).then(compile).then((tc: TemplateFn) => { trace.debug("{0}: compiled", url); callback(cache[url] = tc); }, (err: any) => { diff --git a/src/cjs/ts/di/ResolverHelper.ts b/src/cjs/ts/di/ResolverHelper.ts --- a/src/cjs/ts/di/ResolverHelper.ts +++ b/src/cjs/ts/di/ResolverHelper.ts @@ -4,10 +4,10 @@ import { TraceSource } from "../log/Trac const trace = TraceSource.get(module.id); const mainModule = require.main; -const mainRequire = (id: string) => mainModule.require(id); +const mainRequire = (id: string) => mainModule ? mainModule.require(id) : require; class ModuleResolver { - _base: string; + _base: string | undefined; _require: NodeRequireFunction; constructor(req: NodeRequireFunction, base?: string) { diff --git a/src/main/ts/Cancellation.ts b/src/main/ts/Cancellation.ts --- a/src/main/ts/Cancellation.ts +++ b/src/main/ts/Cancellation.ts @@ -3,7 +3,7 @@ import { argumentNotNull, destroyed } fr export class Cancellation implements ICancellation { private _reason: any; - private _cbs: Array<(e: any) => void>; + private _cbs: Array<(e: any) => void> | undefined; constructor(action: (cancel: (e?: any) => void) => void) { argumentNotNull(action, "action"); @@ -44,7 +44,7 @@ export class Cancellation implements ICa } } - private _unregister(cb) { + private _unregister(cb: any) { if (this._cbs) { const i = this._cbs.indexOf(cb); if (i >= 0) @@ -52,7 +52,7 @@ export class Cancellation implements ICa } } - private _cancel(reason) { + private _cancel(reason: any) { if (this._reason) return; @@ -60,7 +60,7 @@ export class Cancellation implements ICa if (this._cbs) { this._cbs.forEach(cb => cb(reason)); - this._cbs = null; + this._cbs = undefined; } } diff --git a/src/main/ts/EventProvider.ts b/src/main/ts/EventProvider.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/EventProvider.ts @@ -0,0 +1,41 @@ +import { IDestroyable } from "./interfaces"; +import { Observable } from "./Observable"; + +/** + * Event proviers are used to produce events, throug this object you can feed + * the Observable with input events. Once the EventProvider is destroyed the + * bound obsevable is disconnected and marked as 'done'. + */ +export class EventProvider implements IDestroyable { + + _observable: Observable | undefined; + + _next: ((evt: T) => void) | undefined; + _done: (() => void) | undefined; + + constructor() { + this._observable = new Observable((next, _error, done) => { + this._next = next; + this._done = done; + }); + } + + destroy(): void { + if (this._observable) { + // break all references + this._observable = undefined; + this._next = undefined; + this._done = undefined; + } + } + post(event: T) { + return this._next && this._next(event); + } + + getObservable() { + if (!this._observable) + throw new Error("The object is destroyed"); + + return this._observable; + } +} diff --git a/src/main/ts/Observable.ts b/src/main/ts/Observable.ts --- a/src/main/ts/Observable.ts +++ b/src/main/ts/Observable.ts @@ -4,10 +4,16 @@ import { argumentNotNull, destroyed } fr type Handler = (x: T) => void; -type Initializer = (notify: Handler, error?: (e: any) => void, complete?: () => void) => void; +type Initializer = (notify: Handler, error: (e: any) => void, complete: () => void) => void; const noop = () => { }; +const nulObserver: IObserver = Object.freeze({ + next: noop, + error: noop, + complete: noop +}); + function isObserver(val: any): val is IObserver { return val && (typeof val.next === "function"); } @@ -17,7 +23,7 @@ export class Observable implements IO private _observers = new Array>(); - private _complete: boolean; + private _complete = false; private _error: any; @@ -46,7 +52,7 @@ export class Observable implements IO const me = this; const observer: IObserver & IDestroyable = { - next, + next: next.bind(null), error: error ? error.bind(null) : noop, complete: complete ? complete.bind(null) : noop, @@ -62,30 +68,21 @@ export class Observable implements IO subscribe(next: IObserver | Handler, error?: Handler, complete?: () => void): IDestroyable { if (isObserver(next)) { - const me = this; - const subscription = { - destroy() { - me._removeObserver(next); - } + this._addObserver(next); + return { + destroy: () => this._removeObserver(next) }; - this._addObserver(next); - return subscription; - } else if (next) { + } else { const observer = { - next, - error, - complete + next: next.bind(null), + error: error ? error.bind(null) : noop, + complete: complete ? complete.bind(null) : noop }; - const me = this; - const subscription = { - destroy() { - me._removeObserver(observer); - } + + this._addObserver(observer); + return { + destroy: () => this._removeObserver(observer) }; - this._addObserver(observer); - return subscription; - } else { - return destroyed; } } diff --git a/src/main/ts/ObservableValue.ts b/src/main/ts/ObservableValue.ts --- a/src/main/ts/ObservableValue.ts +++ b/src/main/ts/ObservableValue.ts @@ -17,8 +17,10 @@ export class ObservableValue extends } setValue(value: T) { - this._value = value; - this._notifyNext(value); + if (this._value !== value) { + this._value = value; + this._notifyNext(value); + } } on(next: Handler, error?: Handler, complete?: () => void): IDestroyable { diff --git a/src/main/ts/Uuid.ts b/src/main/ts/Uuid.ts --- a/src/main/ts/Uuid.ts +++ b/src/main/ts/Uuid.ts @@ -6,18 +6,25 @@ // Copyright (c) 2010-2012 Robert Kieffer // MIT License - http://opensource.org/licenses/mit-license.php +import { MapOf } from "./interfaces"; + declare const window: any; -declare const require; -declare const Buffer; +declare const require: any; +declare const Buffer: any; const _window: any = "undefined" !== typeof window ? window : null; +interface WritableArrayLike { + length: number; + [n: number]: T; +} + // Unique ID creation requires a high quality random # generator. We // feature // detect to determine the best RNG source, normalizing to a function // that // returns 128-bits of randomness, since that's what's usually required -let _rng; +let _rng: () => WritableArrayLike = () => []; function setupBrowser() { // Allow for MSIE11 msCrypto @@ -43,9 +50,9 @@ function setupBrowser() { // If all else fails, use Math.random(). It's fast, but is of // unspecified // quality. - const _rnds = new Array(16); + const _rnds = new Array(16); _rng = () => { - for (let i = 0, r; i < 16; i++) { + for (let i = 0, r = 0; i < 16; i++) { if ((i & 0x03) === 0) { r = Math.random() * 0x100000000; } @@ -84,22 +91,22 @@ if (_window) { const BufferClass = ("function" === typeof Buffer) ? Buffer : Array; // Maps for number <-> hex string conversion -const _byteToHex = []; -const _hexToByte = {}; +const _byteToHex: string[] = []; +const _hexToByte: MapOf = {}; for (let i = 0; i < 256; i++) { _byteToHex[i] = (i + 0x100).toString(16).substr(1); _hexToByte[_byteToHex[i]] = i; } // **`parse()` - Parse a UUID into it's component bytes** -function _parse(s, buf?, offset?): Array { +function _parse(s: string, buf: number[] = [], offset?: number): number[] { const i = (buf && offset) || 0; let ii = 0; - buf = buf || []; s.toLowerCase().replace(/[0-9a-f]{2}/g, oct => { if (ii < 16) { // Don't overflow! buf[i + ii++] = _hexToByte[oct]; } + return ""; }); // Zero out remaining bytes if string was short @@ -111,7 +118,7 @@ function _parse(s, buf?, offset?): Array } // **`unparse()` - Convert UUID byte array (ala parse()) into a string** -function _unparse(buf, offset?): string { +function _unparse(buf: ArrayLike, offset?: number): string { let i = offset || 0; const bth = _byteToHex; return bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + "-" + bth[buf[i++]] + bth[buf[i++]] + "-" + @@ -145,13 +152,20 @@ let _clockseq = (_seedBytes[6] << 8 | _s // Previous uuid creation time let _lastMSecs = 0; let _lastNSecs = 0; +interface V1Options { + clockseq?: number; + msecs?: number; + nsecs?: number; + node?: number[]; +} + // See https://github.com/broofa/node-uuid for API details -function _v1(options?, buf?, offset?): string { +function _v1(options?: V1Options): string; +function _v1(options: V1Options, buf: number[], offset?: number): number[]; +function _v1(options: V1Options = {}, buf?: number[], offset?: number): string | number[] { let i = buf && offset || 0; const b = buf || []; - options = options || {}; - let clockseq = (options.clockseq != null) ? options.clockseq : _clockseq; // UUID timestamps are 100 nano-second units since the Gregorian @@ -228,19 +242,21 @@ function _v1(options?, buf?, offset?): s return buf ? buf : _unparse(b); } +interface V4Opptions { + rng?: () => WritableArrayLike; + + random?: number[]; +} + // **`v4()` - Generate random UUID** // See https://github.com/broofa/node-uuid for API details -function _v4(options?, buf?, offset?): string { +function _v4(options?: V4Opptions): string; +function _v4(options: V4Opptions, buf: number[], offset?: number): number[]; +function _v4(options: V4Opptions = {}, buf?: number[], offset?: number): string | number[] { // Deprecated - 'format' argument, as supported in v1.2 const i = buf && offset || 0; - if (typeof (options) === "string") { - buf = (options === "binary") ? new BufferClass(16) : null; - options = null; - } - options = options || {}; - const rnds = options.random || (options.rng || _rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` diff --git a/src/main/ts/components/ActivatableMixin.ts b/src/main/ts/components/ActivatableMixin.ts --- a/src/main/ts/components/ActivatableMixin.ts +++ b/src/main/ts/components/ActivatableMixin.ts @@ -9,15 +9,21 @@ const log = TraceSource.get("@implab/cor export function ActivatableMixin>(Base: TBase) { return class extends Base implements IActivatable { - _controller: IActivationController; + _controller: IActivationController | undefined; - _active: boolean; + _active = false; isActive() { return this._active; } + hasActivationController() { + return !!this._controller; + } + getActivationController() { + if (!this._controller) + throw Error("Activation controller isn't set"); return this._controller; } diff --git a/src/main/ts/components/AsyncComponent.ts b/src/main/ts/components/AsyncComponent.ts --- a/src/main/ts/components/AsyncComponent.ts +++ b/src/main/ts/components/AsyncComponent.ts @@ -3,7 +3,7 @@ import { IAsyncComponent, ICancellation, import { destroy } from "../safe"; export class AsyncComponent implements IAsyncComponent, ICancellable { - _cancel: (e: any) => void; + _cancel: ((e: any) => void) | undefined; _completion: Promise = Promise.resolve(); @@ -26,7 +26,7 @@ export class AsyncComponent implements I // after the operation is complete we need to cleanup the // resources destroy(h); - this._cancel = null; + this._cancel = undefined; } }; diff --git a/src/main/ts/di/ActivationContext.ts b/src/main/ts/di/ActivationContext.ts --- a/src/main/ts/di/ActivationContext.ts +++ b/src/main/ts/di/ActivationContext.ts @@ -1,6 +1,6 @@ import { TraceSource } from "../log/TraceSource"; import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe"; -import { Descriptor, ServiceMap } from "./interfaces"; +import { Descriptor, ServiceMap, PartialServiceMap } from "./interfaces"; import { Container } from "./Container"; import { MapOf } from "../interfaces"; @@ -11,13 +11,13 @@ export interface ActivationContextInfo; + scope: PartialServiceMap; } export class ActivationContext { _cache: MapOf; - _services: ServiceMap; + _services: PartialServiceMap; _stack: ActivationContextInfo[]; @@ -29,7 +29,7 @@ export class ActivationContext { container: Container; - constructor(container: Container, services: ServiceMap, name?: string, cache?: object, visited?: MapOf) { + constructor(container: Container, services: PartialServiceMap, name?: string, cache?: object, visited?: MapOf) { argumentNotNull(container, "container"); argumentNotNull(services, "services"); @@ -45,11 +45,11 @@ export class ActivationContext { return this._name; } - resolve(name: K, def?: T) { + resolve(name: K, def?: T): T { const d = this._services[name]; if (d !== undefined) { - return this.activate(d as Descriptor, name.toString()); + return this.activate(d as Descriptor, name.toString()); } else { if (def !== undefined && def !== null) return def; @@ -64,14 +64,14 @@ export class ActivationContext { * @name{string} the name of the service * @service{string} the service descriptor to register */ - register(name: K, service: Descriptor) { + register(name: K, service: Descriptor) { argumentNotEmptyString(name, "name"); this._services[name] = service; } clone() { - return new ActivationContext( + return new ActivationContext( this.container, this._services, this._name, @@ -92,12 +92,12 @@ export class ActivationContext { return (this._cache[id] = value); } - activate(d: Descriptor, name: string) { + activate(d: Descriptor, name: string) { if (trace.isLogEnabled()) trace.log(`enter ${name} ${d}`); this.enter(name, d.toString()); - const v = d.activate(this); + const v = d.activate(this); this.leave(); if (trace.isLogEnabled()) diff --git a/src/main/ts/di/AggregateDescriptor.ts b/src/main/ts/di/AggregateDescriptor.ts --- a/src/main/ts/di/AggregateDescriptor.ts +++ b/src/main/ts/di/AggregateDescriptor.ts @@ -1,11 +1,7 @@ -import { Descriptor, isDescriptor } from "./interfaces"; +import { Descriptor, isDescriptor, Parse } from "./interfaces"; import { ActivationContext } from "./ActivationContext"; import { isPrimitive } from "../safe"; -type Parse = T extends Descriptor ? V : - T extends {} ? { [K in keyof T]: Parse } : - T; - export class AggregateDescriptor implements Descriptor> { _value: T; diff --git a/src/main/ts/di/Annotations.ts b/src/main/ts/di/Annotations.ts --- a/src/main/ts/di/Annotations.ts +++ b/src/main/ts/di/Annotations.ts @@ -37,7 +37,6 @@ export declare function services { consume

(...args: P) { return ) => T>(constructor: C) => { - return constructor; }; } @@ -57,5 +56,3 @@ export class Builder { } } - - diff --git a/src/main/ts/di/Configuration.ts b/src/main/ts/di/Configuration.ts --- a/src/main/ts/di/Configuration.ts +++ b/src/main/ts/di/Configuration.ts @@ -10,7 +10,8 @@ import { ActivationType, isValueRegistration, isTypeRegistration, - isFactoryRegistration + isFactoryRegistration, + PartialServiceMap } from "./interfaces"; import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe"; @@ -27,8 +28,9 @@ import { makeResolver } from "./Resolver import { ICancellation } from "../interfaces"; const trace = TraceSource.get("@implab/core/di/Configuration"); - -async function mapAll(data: any | any[], map?: (v: any, k: number | string) => any): Promise { +async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise; +async function mapAll(data: any, map?: (v: any, k: string) => any): Promise; +async function mapAll(data: any, map?: (v: any, k: any) => any): Promise { if (data instanceof Array) { return Promise.all(map ? data.map(map) : data); } else { @@ -101,7 +103,7 @@ export class Configuration { if (resolver) this._require = resolver; - let services: ServiceMap; + let services: PartialServiceMap; try { services = await this._visitRegistrations(data, "$"); @@ -155,20 +157,20 @@ export class Configuration { data.constructor.prototype !== Object.prototype) throw new Error("Configuration must be a simple object"); - const o: ServiceMap = {}; + const o: PartialServiceMap = {}; const keys = Object.keys(data); const services = await mapAll(data, async (v, k) => { const d = await this._visit(v, k.toString()); return isDescriptor(d) ? d : new AggregateDescriptor(d); - }) as ServiceMap; + }) as PartialServiceMap; this._leave(); return services; } - _enter(name: keyof any) { + _enter(name: string) { this._path.push(name.toString()); trace.debug(">{0}", name); } @@ -178,7 +180,7 @@ export class Configuration { trace.debug("<{0}", name); } - async _visit(data: T, name: string) { + async _visit(data: T, name: string): Promise { if (isPrimitive(data) || isDescriptor(data)) return data; diff --git a/src/main/ts/di/Container.ts b/src/main/ts/di/Container.ts --- a/src/main/ts/di/Container.ts +++ b/src/main/ts/di/Container.ts @@ -1,7 +1,7 @@ import { ActivationContext } from "./ActivationContext"; import { ValueDescriptor } from "./ValueDescriptor"; import { ActivationError } from "./ActivationError"; -import { isDescriptor, ServiceMap, Descriptor } from "./interfaces"; +import { isDescriptor, ServiceMap, Descriptor, PartialServiceMap, ContainerServices, Resolver } from "./interfaces"; import { TraceSource } from "../log/TraceSource"; import { Configuration } from "./Configuration"; import { Cancellation } from "../Cancellation"; @@ -9,8 +9,8 @@ import { MapOf } from "../interfaces"; const trace = TraceSource.get("@implab/core/di/ActivationContext"); -export class Container }> { - readonly _services: ServiceMap; +export class Container implements Resolver { + readonly _services: PartialServiceMap>; readonly _cache: MapOf; @@ -28,7 +28,7 @@ export class Container(name: K, def?: T) { + resolve, T extends ContainerServices[K] = ContainerServices[K]>(name: K, def?: T): T { trace.debug("resolve {0}", name); const d = this._services[name]; if (d === undefined) { @@ -52,7 +52,7 @@ export class Container(this, this._services); try { - return context.activate(d as Descriptor, name.toString()); + return context.activate(d as Descriptor, name.toString()); } catch (error) { throw new ActivationError(name.toString(), context.getStack(), error); } @@ -62,25 +62,26 @@ export class Container(name: K, def?: S[K]) { + getService[K] = ContainerServices[K]>(name: K, def?: T) { return this.resolve(name, def); } - register(name: K, service: Descriptor): this; - register(services: ServiceMap): this; - register(nameOrCollection: K | ServiceMap, service?: Descriptor) { + register(name: K, service: Descriptor): this; + register(services: PartialServiceMap): this; + register(nameOrCollection: K | ServiceMap, service?: Descriptor) { if (arguments.length === 1) { const data = nameOrCollection as ServiceMap; + for (const name in data) { if (Object.prototype.hasOwnProperty.call(data, name)) { - this.register(name, data[name] as Descriptor); + this.register(name, data[name] as Descriptor); } } } else { if (!isDescriptor(service)) throw new Error("The service parameter must be a descriptor"); - this._services[nameOrCollection as K] = service; + this._services[nameOrCollection as K] = service as any; } return this; } @@ -110,7 +111,7 @@ export class Container(this); if (typeof (config) === "string") { return c.loadConfiguration(config, opts && opts.contextRequire, ct); diff --git a/src/main/ts/di/FactoryServiceDescriptor.ts b/src/main/ts/di/FactoryServiceDescriptor.ts --- a/src/main/ts/di/FactoryServiceDescriptor.ts +++ b/src/main/ts/di/FactoryServiceDescriptor.ts @@ -1,20 +1,19 @@ import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; -import { Factory } from "../interfaces"; import { argumentNotNull, oid } from "../safe"; import { ActivationType } from "./interfaces"; -export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams { - factory: Factory; +export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams { + factory: (...args: P) => T; } -export class FactoryServiceDescriptor extends ServiceDescriptor { - constructor(opts: FactoryServiceDescriptorParams) { +export class FactoryServiceDescriptor extends ServiceDescriptor { + constructor(opts: FactoryServiceDescriptorParams) { super(opts); argumentNotNull(opts && opts.factory, "opts.factory"); // bind to null - this._factory = (...args) => opts.factory.apply(null, args); + this._factory = (...args) => opts.factory.apply(null, args as any); if (opts.activation === ActivationType.Singleton) { this._cacheId = oid(opts.factory); diff --git a/src/main/ts/di/ReferenceDescriptor.ts b/src/main/ts/di/ReferenceDescriptor.ts --- a/src/main/ts/di/ReferenceDescriptor.ts +++ b/src/main/ts/di/ReferenceDescriptor.ts @@ -1,6 +1,6 @@ import { isNull, argumentNotEmptyString, each, keys } from "../safe"; import { ActivationContext } from "./ActivationContext"; -import { ServiceMap, Descriptor } from "./interfaces"; +import { ServiceMap, Descriptor, PartialServiceMap } from "./interfaces"; import { ActivationError } from "./ActivationError"; export interface ReferenceDescriptorParams { @@ -8,23 +8,23 @@ export interface ReferenceDescriptorPara lazy?: boolean; optional?: boolean; default?: S[K]; - services?: ServiceMap; + services?: PartialServiceMap; } -function def(v: T) { +function defined(v: T | undefined) { if (v === undefined) throw Error(); return v; } -export class ReferenceDescriptor implements Descriptor { +export class ReferenceDescriptor implements Descriptor ) => S[K])> { _name: K; _lazy = false; _optional = false; - _default: any; + _default: S[K] | undefined; _services: ServiceMap; @@ -35,25 +35,25 @@ export class ReferenceDescriptor; } activate(context: ActivationContext) { // добавляем сервисы if (this._services) { - each(this._services, (v, k) => context.register(k, def(v))); + each(this._services, (v, k) => context.register(k, v)); } if (this._lazy) { const saved = context.clone(); - return (cfg: Partial>) => { + return (cfg?: PartialServiceMap) => { // защищаем контекст на случай исключения в процессе // активации const ct = saved.clone(); try { if (cfg) { - each(cfg, (v, k) => ct.register(k, v || {})); + each(cfg as ServiceMap, (v, k) => ct.register(k, v)); } return this._optional ? ct.resolve(this._name, this._default) : ct @@ -89,9 +89,9 @@ export class ReferenceDescriptor(target: T, method: M, context: ActivationContext, args: A) { + const m = target[method]; - if (!m) + if (!m || typeof m !== "function") throw new Error("Method '" + method + "' not found"); if (args instanceof Array) @@ -19,7 +20,8 @@ function injectMethod(target, method, co return m.call(target, _parse(args, context, "." + method)); } -function makeClenupCallback(target, method: ((instance) => void) | string) { +function makeClenupCallback(target: T, method: Cleaner): () => void; +function makeClenupCallback(target: any, method: any) { if (typeof (method) === "string") { return () => { target[method](); @@ -31,10 +33,9 @@ function makeClenupCallback(target, meth } } -// TODO: make async -function _parse(value, context: ActivationContext, path: string) { +function _parse(value: T, context: ActivationContext, path: string): Parse { if (isPrimitive(value)) - return value; + return value as any; trace.debug("parse {0}", path); @@ -42,65 +43,69 @@ function _parse(value, context: Activati return context.activate(value, path); if (value instanceof Array) - return value.map((x, i) => _parse(x, context, `${path}[${i}]`)); + return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any; - const t = {}; - for (const p of Object.keys(value)) - t[p] = _parse(value[p], context, `${path}.${p}`); + const t: any = {}; + + keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`)); return t; } -export interface ServiceDescriptorParams { +export type Cleaner = ((x: T) => void) | keyof Extract void }>; + +export type InjectionSpec = { + [m in keyof T]?: any; +}; + +export interface ServiceDescriptorParams { activation?: ActivationType; - owner: Container; + owner: Container; - params?; + params?: P; - inject?: object[]; + inject?: InjectionSpec[]; - services?: ServiceMap; + services?: PartialServiceMap; - cleanup?: ((x) => void) | string; + cleanup?: Cleaner; } -export class ServiceDescriptor implements Descriptor { - _instance; +export class ServiceDescriptor implements Descriptor { + _instance: T | undefined; _hasInstance = false; _activationType = ActivationType.Call; - _services: ServiceMap; + _services: ServiceMap; - _params; + _params: P | undefined; - _inject: object[]; + _inject: InjectionSpec[]; - _cleanup: ((x) => void) | string; + _cleanup: Cleaner | undefined; _cacheId: any; - _owner: Container; + _owner: Container; - constructor(opts: ServiceDescriptorParams) { + constructor(opts: ServiceDescriptorParams) { argumentNotNull(opts, "opts"); argumentNotNull(opts.owner, "owner"); this._owner = opts.owner; - if ("activation" in opts) + if (!isNull(opts.activation)) this._activationType = opts.activation; - if ("params" in opts) + if (!isNull(opts.params)) this._params = opts.params; - if (opts.inject) - this._inject = opts.inject; + this._inject = opts.inject || []; - if (opts.services) - this._services = opts.services; + this._services = (opts.services || {}) as ServiceMap; if (opts.cleanup) { if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function)) @@ -111,9 +116,9 @@ export class ServiceDescriptor implement } } - activate(context: ActivationContext) { + activate(context: ActivationContext) { // if we have a local service records, register them first - let instance; + let instance: T; // ensure we have a cache id if (!this._cacheId) @@ -197,11 +202,11 @@ export class ServiceDescriptor implement return this._instance; } - _factory(...params: any[]): any { + _factory(...params: any[]): T { throw Error("Not implemented"); } - _create(context: ActivationContext) { + _create(context: ActivationContext) { trace.debug(`constructing ${context._name}`); if (this._activationType !== ActivationType.Call && @@ -209,11 +214,10 @@ export class ServiceDescriptor implement throw new Error("Recursion detected"); if (this._services) { - for (const p in this._services) - context.register(p, this._services[p]); + keys(this._services).forEach(p => context.register(p, this._services[p])); } - let instance; + let instance: T; if (this._params === undefined) { instance = this._factory(); diff --git a/src/main/ts/di/TypeServiceDescriptor.ts b/src/main/ts/di/TypeServiceDescriptor.ts --- a/src/main/ts/di/TypeServiceDescriptor.ts +++ b/src/main/ts/di/TypeServiceDescriptor.ts @@ -2,14 +2,14 @@ import { ServiceDescriptor, ServiceDescr import { Constructor, Factory } from "../interfaces"; import { argumentNotNull, isPrimitive } from "../safe"; -export interface TypeServiceDescriptorParams extends ServiceDescriptorParams { - type: Constructor; +export interface TypeServiceDescriptorParams extends ServiceDescriptorParams { + type: Constructor; } -export class TypeServiceDescriptor extends ServiceDescriptor { +export class TypeServiceDescriptor extends ServiceDescriptor { _type: Constructor; - constructor(opts: TypeServiceDescriptorParams) { + constructor(opts: TypeServiceDescriptorParams) { super(opts); argumentNotNull(opts && opts.type, "opts.type"); diff --git a/src/main/ts/di/ValueDescriptor.ts b/src/main/ts/di/ValueDescriptor.ts --- a/src/main/ts/di/ValueDescriptor.ts +++ b/src/main/ts/di/ValueDescriptor.ts @@ -1,6 +1,6 @@ import { Descriptor } from "./interfaces"; -export class ValueDescriptor implements Descriptor { +export class ValueDescriptor implements Descriptor { _value: T; constructor(value: T) { diff --git a/src/main/ts/di/interfaces.ts b/src/main/ts/di/interfaces.ts --- a/src/main/ts/di/interfaces.ts +++ b/src/main/ts/di/interfaces.ts @@ -1,9 +1,9 @@ -import { isPrimitive } from "../safe"; +import { isPrimitive, primitive } from "../safe"; import { ActivationContext } from "./ActivationContext"; import { Constructor, Factory } from "../interfaces"; -export interface Descriptor { - activate(context: ActivationContext): T; +export interface Descriptor { + activate(context: ActivationContext): T; } export function isDescriptor(x: any): x is Descriptor { @@ -11,9 +11,11 @@ export function isDescriptor(x: any): x (x.activate instanceof Function); } -export type ServiceMap = { - [k in keyof S]: Descriptor; -} +export type ServiceMap = { + [k in keyof S2]: Descriptor; +}; + +export type PartialServiceMap = Partial>; export enum ActivationType { Singleton = 1, @@ -58,6 +60,17 @@ export interface DependencyRegistration< default?: S[K]; } +export type Parse = T extends primitive ? T: + T extends Descriptor ? V : + { [K in keyof T]: Parse }; + +export interface Resolver { + resolve, T extends ContainerServices[K] = ContainerServices[K]>(name: K, def?: T): T; +} +export type ContainerServices = S & { + container: Resolver; +}; + export function isTypeRegistration(x: any): x is TypeRegistration { return (!isPrimitive(x)) && ("$type" in x); } diff --git a/src/main/ts/interfaces.ts b/src/main/ts/interfaces.ts --- a/src/main/ts/interfaces.ts +++ b/src/main/ts/interfaces.ts @@ -57,6 +57,9 @@ export interface IActivatable { */ setActivationController(controller: IActivationController): void; + /** Indicates whether this component has an activation controller */ + hasActivationController(): boolean; + /** * Gets the current activation controller for this component */ @@ -76,6 +79,8 @@ export interface IActivationController { activate(component: IActivatable, ct?: ICancellation): Promise; + hasActive(): boolean; + getActive(): IActivatable; } diff --git a/src/main/ts/log/Registry.ts b/src/main/ts/log/Registry.ts --- a/src/main/ts/log/Registry.ts +++ b/src/main/ts/log/Registry.ts @@ -1,15 +1,15 @@ import { TraceSource } from "./TraceSource"; import { argumentNotNull } from "../safe"; -import { IDestroyable } from "../interfaces"; +import { IDestroyable, MapOf } from "../interfaces"; export class Registry { static readonly instance = new Registry(); - private _registry: object = new Object(); - private _listeners: object = new Object(); + private _registry: MapOf = {}; + private _listeners: MapOf<(source: TraceSource) => void> = {}; private _nextCookie: number = 1; - get(id: any): TraceSource { + get(id: string): TraceSource { argumentNotNull(id, "id"); if (this._registry[id]) diff --git a/src/main/ts/log/TraceEventData.ts b/src/main/ts/log/TraceEventData.ts --- a/src/main/ts/log/TraceEventData.ts +++ b/src/main/ts/log/TraceEventData.ts @@ -5,7 +5,7 @@ export class TraceEventData implements T source: TraceSource; level: number; message: any; - args?: any[]; + args: any[]; constructor(source: TraceSource, level: number, message: any, args: any[]) { this.source = source; diff --git a/src/main/ts/log/TraceSource.ts b/src/main/ts/log/TraceSource.ts --- a/src/main/ts/log/TraceSource.ts +++ b/src/main/ts/log/TraceSource.ts @@ -1,6 +1,7 @@ import { Observable } from "../Observable"; import { Registry } from "./Registry"; import { TraceEventData } from "./TraceEventData"; +import { EventProvider } from "../EventProvider"; export const DebugLevel = 400; @@ -25,22 +26,21 @@ export interface TraceEvent { export class TraceSource { readonly id: any; - level: number; + level = 0; readonly events: Observable; - _notifyNext: (arg: TraceEvent) => void; + private readonly _provider: EventProvider; - constructor(id: any) { + constructor(id?: any) { this.id = id || new Object(); - this.events = new Observable(next => { - this._notifyNext = next; - }); + this._provider = new EventProvider(); + this.events = this._provider.getObservable(); } - protected emit(level: number, message: any, args?: any[]) { - this._notifyNext(new TraceEventData(this, level, message, args)); + protected emit(level: number, message: any, args: any[]) { + this._provider.post(new TraceEventData(this, level, message, args)); } isDebugEnabled() { diff --git a/src/main/ts/log/writers/ConsoleLogger.ts b/src/main/ts/log/writers/ConsoleLogger.ts --- a/src/main/ts/log/writers/ConsoleLogger.ts +++ b/src/main/ts/log/writers/ConsoleLogger.ts @@ -32,7 +32,11 @@ export class ConsoleLogger implements ID this._writer.setLogLevel("error"); } this._writer.write("{0}: ", next.source.id); - this._writer.writeLine(next.message, ...next.args); + + if (next.args) + this._writer.writeLine(next.message, ...next.args); + else + this._writer.writeLine(next.message); } destroy() { diff --git a/src/main/ts/safe.ts b/src/main/ts/safe.ts --- a/src/main/ts/safe.ts +++ b/src/main/ts/safe.ts @@ -20,6 +20,10 @@ export function keys(arg: T): (Extrac return isObject(arg) && arg ? Object.keys(arg) as (Extract)[] : []; } +export function isKeyof(k: string, target: T): k is Extract { + return target && typeof target === "object" && k in target; +} + export function argumentNotNull(arg: any, name: string) { if (arg === null || arg === undefined) throw new Error("The argument " + name + " can't be null or undefined"); @@ -44,11 +48,13 @@ export function isObject(val: any): val return typeof val === "object"; } -export function isNull(val: any) { +export function isNull(val: any): val is null | undefined { return (val === null || val === undefined); } -export function isPrimitive(val: any): val is string | number | boolean | undefined | null { +export type primitive = symbol | string | number | boolean | undefined | null; + +export function isPrimitive(val: any): val is primitive { return (val === null || val === undefined || typeof (val) === "string" || typeof (val) === "number" || typeof (val) === "boolean"); } @@ -65,7 +71,7 @@ export function isString(val: any): val return typeof (val) === "string" || val instanceof String; } -export function isPromise(val: any): val is PromiseLike { +export function isPromise(val: any): val is PromiseLike { return val && typeof val.then === "function"; } @@ -73,12 +79,12 @@ export function isCancellable(val: any): return val && typeof val.cancel === "function"; } -export function isNullOrEmptyString(val: any): val is (string | null | undefined) { +export function isNullOrEmptyString(val: any): val is ("" | null | undefined) { return (val === null || val === undefined || - ((typeof (val) === "string" || val instanceof String) && val.length === 0)) + ((typeof (val) === "string" || val instanceof String) && val.length === 0)); } -export function isNotEmptyArray(arg: any): arg is Array { +export function isNotEmptyArray(arg: any): arg is T[] { return (arg instanceof Array && arg.length > 0); } @@ -126,7 +132,8 @@ export function get(member: string, cont * @returns Результат вызова функции cb, либо undefined * если достигнут конец массива. */ -export function each(obj: T, cb: (v: T[keyof T], k: keyof T) => void): void; +export function each(obj: T, cb: (v: NonNullable, k: X) => void): void; +export function each(array: T[], cb: (v: T, i: number) => void): void; export function each(obj: any, cb: any, thisArg?: any): any; export function each(obj: any, cb: any, thisArg?: any) { argumentNotNull(cb, "cb"); @@ -162,28 +169,27 @@ export function each(obj: any, cb: any, * own properties of the source are entirely copied to the destination. * */ -export function mixin(dest: T, source: S, template?: string[] | object): T & S { - argumentNotNull(dest, "to"); - const _res = dest as T & S; +export function mixin(dest: T, source: S, template?: keyof S[]): T & S; +export function mixin(dest: T, source: S, template: { [p in keyof S]?: keyof R; }): T & R; +export function mixin(dest: T, source: S, template?: any): any { + argumentNotNull(dest, "dest"); + const _res: any = dest as any; if (isPrimitive(source)) return _res; if (template instanceof Array) { - for (const p of template) { - if (p in source) + template.forEach(p => { + if (isKeyof(p, source)) _res[p] = source[p]; - } + }); } else if (template) { - const _keys = Object.keys(source); - for (const p of _keys) { - if (p in template) + keys(source).forEach(p => { + if (isKeyof(p, template)) _res[template[p]] = source[p]; - } + }); } else { - const keys = Object.keys(source); - for (const p of keys) - _res[p] = source[p]; + keys(source).forEach(p => _res[p] = source[p]); } return _res; @@ -193,7 +199,15 @@ export function mixin any, thisArg: any): (...args: any[]) => PromiseLike { +export function async T | PromiseLike>( + fn: F, + thisArg?: ThisParameterType +): (...args: Parameters) => PromiseLike; +export function async T | PromiseLike }>( + fn: M, + thisArg: O +): (...args: Parameters>) => PromiseLike; +export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike { let fn = _fn; if (arguments.length === 2 && !(fn instanceof Function)) @@ -202,7 +216,7 @@ export function async(_fn: (...args: any if (fn == null) throw new Error("The function must be specified"); - function wrapresult(x, e?): PromiseLike { + function wrapresult(x: any, e?: any): PromiseLike { if (e) { return { then(cb, eb) { @@ -237,10 +251,16 @@ export function async(_fn: (...args: any }; } -type _AnyFn = (...args) => any; - -export function delegate(target: T, _method: (K | _AnyFn)) { - let method; +export function delegate any>( + target: T, + method: F +): OmitThisParameter; +export function delegate any; }>( + target: T, + method: M +): OmitThisParameter; +export function delegate(target: any, _method: any): (...args: any[]) => any { + let method: any; if (!(_method instanceof Function)) { argumentNotNull(target, "target"); method = target[_method]; @@ -271,6 +291,19 @@ export function delay(timeMs: number, ct }); } +/** Returns resolved promise, awaiting this method will cause the asynchronous + * completion of the rest of the code. + */ +export function fork() { + return Promise.resolve(); +} + +/** Always throws Error, can be used as a stub for the methods which should be + * assigned later and are required to be not null. + */ +export function notImplemented(): never { + throw new Error("Not implemeted"); +} /** * Iterates over the specified array of items and calls the callback `cb`, if * the result of the callback is a promise the next item from the array will be @@ -345,7 +378,7 @@ export function first(sequence: Array export function first(sequence: PromiseLike>): PromiseLike; export function first( sequence: ArrayLike | PromiseLike>, - cb: (x: T) => void, + cb?: (x: T) => void, err?: (x: Error) => void ): void; /** @@ -367,7 +400,7 @@ export function first( err?: (x: Error) => void ) { if (isPromise(sequence)) { - return sequence.then(res => first(res, cb, err)); + return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err)); } else if (sequence && "length" in sequence) { if (sequence.length === 0) { if (err) @@ -409,7 +442,12 @@ export function firstWhere( err?: (x: Error) => any ) { if (isPromise(sequence)) { - return sequence.then(res => firstWhere(res, predicate, cb, err)); + return sequence.then(res => firstWhere( + res, + predicate as any /* force to pass undefined predicate */, + cb as any /* force to pass undefined cb */, + err) + ); } else if (sequence && "length" in sequence) { if (sequence.length === 0) { if (err) diff --git a/src/main/ts/text/Converter.ts b/src/main/ts/text/Converter.ts --- a/src/main/ts/text/Converter.ts +++ b/src/main/ts/text/Converter.ts @@ -3,9 +3,9 @@ import { isPrimitive, isNull } from "../ export class Converter { static readonly default = new Converter(); - convert(value: any, pattern: string) { + convert(value: any, pattern?: string) { if (pattern && pattern.toLocaleLowerCase() === "json") { - const seen = []; + const seen: any[] = []; return JSON.stringify(value, (k, v) => { if (!isPrimitive(v)) { const id = seen.indexOf(v); diff --git a/src/main/ts/text/FormatCompiler.ts b/src/main/ts/text/FormatCompiler.ts --- a/src/main/ts/text/FormatCompiler.ts +++ b/src/main/ts/text/FormatCompiler.ts @@ -6,29 +6,37 @@ type CompiledPattern = (writer: TextWrit export class FormatCompiler { _scanner: FormatScanner; - _cache: MapOf = {}; + static _cache: MapOf = {}; + + _parts: Array; + + constructor(scanner: FormatScanner) { + this._scanner = scanner; + this._parts = []; + } - _parts: Array; + compile() { + this.visitText(); + const parts = this._parts; - compile(pattern: string) { + return (writer: TextWriter, args: any) => { + parts.forEach(x => { + if (isPrimitive(x)) + writer.writeValue(x); + else + writer.writeValue(get(x.name, args), x.format); + }); + }; + } + + static compile(pattern: string) { let compiledPattern = this._cache && this._cache[pattern]; if (!compiledPattern) { - this._scanner = new FormatScanner(pattern); - this._parts = []; - - this.visitText(); - const parts = this._parts; + const compiler = new this(new FormatScanner(pattern)); - compiledPattern = (writer: TextWriter, args: any) => { - parts.forEach(x => { - if (isPrimitive(x)) - writer.writeValue(x); - else - writer.writeValue(get(x.name, args), x.format); - }); - }; - if (this._cache) - this._cache[pattern] = compiledPattern; + compiledPattern = compiler.compile(); + + this._cache[pattern] = compiledPattern; } return compiledPattern; } @@ -73,7 +81,7 @@ export class FormatCompiler { this.dieUnexpectedToken("TEXT"); const fieldName = this._scanner.getTokenValue(); - const filedFormat = this.readColon() ? this.readFieldFormat() : null; + const filedFormat = this.readColon() ? this.readFieldFormat() : undefined; if (this._scanner.getTokenType() !== TokeType.CurlClose) this.dieUnexpectedToken("}"); @@ -104,7 +112,7 @@ export class FormatCompiler { return true; } - pushSubst(fieldName: string, filedFormat: string) { + pushSubst(fieldName: string, filedFormat?: string) { // console.log("pushSubst ", fieldName, filedFormat); this._parts.push({ name: fieldName, format: filedFormat }); } @@ -113,14 +121,14 @@ export class FormatCompiler { this._parts.push(text); } - dieUnexpectedToken(expected?: string) { + dieUnexpectedToken(expected?: string): never { throw new Error(isNullOrEmptyString(expected) ? `Unexpected token ${this._scanner.getTokenValue()}` : `Unexpected token ${this._scanner.getTokenValue()}, expected ${expected}` ); } - dieUnexpectedEnd(expected?: string) { + dieUnexpectedEnd(expected?: string): never { throw new Error(isNullOrEmptyString(expected) ? "Unexpected end of the string" : `Unexpected end of the string, expected ${expected}`); } } diff --git a/src/main/ts/text/FormatScanner.ts b/src/main/ts/text/FormatScanner.ts --- a/src/main/ts/text/FormatScanner.ts +++ b/src/main/ts/text/FormatScanner.ts @@ -16,8 +16,8 @@ const typeMap = { export class FormatScanner { private _text: string; - private _tokenType: TokeType; - private _tokenValue: string; + private _tokenType: TokeType | undefined; + private _tokenValue: string | undefined; private _rx = /[^{}:]+|(.)/g; constructor(text: string) { @@ -30,6 +30,9 @@ export class FormatScanner { return false; const match = this._rx.exec(this._text); + if (match === null) + return false; + this._tokenType = typeMap[match[1]] || TokeType.Text; this._tokenValue = match[0]; @@ -37,10 +40,15 @@ export class FormatScanner { } getTokenValue() { + if (this._tokenValue === undefined) + throw new Error("The scanner is before the first element"); return this._tokenValue; } getTokenType() { + + if (this._tokenType === undefined) + throw new Error("The scanner is before the first element"); return this._tokenType; } } diff --git a/src/main/ts/text/StringBuilder.ts b/src/main/ts/text/StringBuilder.ts --- a/src/main/ts/text/StringBuilder.ts +++ b/src/main/ts/text/StringBuilder.ts @@ -26,6 +26,6 @@ const sb = new StringBuilder(); export function format(format: string, ...args: any): string; export function format() { sb.clear(); - sb.write.apply(sb, arguments); + sb.write.apply(sb, arguments); return sb.toString(); } diff --git a/src/main/ts/text/StringFormat.ts b/src/main/ts/text/StringFormat.ts --- a/src/main/ts/text/StringFormat.ts +++ b/src/main/ts/text/StringFormat.ts @@ -1,8 +1,8 @@ -import { isPrimitive, isNull, each } from "../safe"; +import { isPrimitive, isNull, each, isKeyof, get } from "../safe"; import { MapOf } from "../interfaces"; type SubstFn = (name: string, format?: string) => string; -type TemplateFn = (subst: SubstFn) => string; +type TemplateFn = (subst: SubstFn) => string | undefined; type ConvertFn = (value: any, format?: string) => string; const map = { @@ -28,19 +28,19 @@ function espaceString(s: string) { function encode(s: string) { if (!s) return s; - return s.replace(/\\{|\\}|&|\\:|\n/g, m => map[m] || m); + return s.replace(/\\{|\\}|&|\\:|\n/g, m => isKeyof(m, map) ? map[m] : m); } function decode(s: string) { if (!s) return s; - return s.replace(/&(\w+);/g, (m, $1) => rev[$1] || m); + return s.replace(/&(\w+);/g, (m, $1) => isKeyof($1, rev) ? rev[$1] : m); } function subst(s: string) { const i = s.indexOf(":"); let name: string; - let pattern: string; + let pattern: string | undefined; if (i >= 0) { name = s.substr(0, i); pattern = s.substr(i + 1); @@ -51,7 +51,8 @@ function subst(s: string) { if (pattern) return [ espaceString(decode(name)), - espaceString(decode(pattern))]; + espaceString(decode(pattern)) + ]; else return [espaceString(decode(name))]; } @@ -103,9 +104,9 @@ export function compile(template: string return compiled; } -function defaultConverter(value: any, pattern: string) { +function defaultConverter(value: any, pattern?: string) { if (pattern && pattern.toLocaleLowerCase() === "json") { - const seen = []; + const seen: any = []; return JSON.stringify(value, (k, v) => { if (!isPrimitive(v)) { const id = seen.indexOf(v); @@ -113,10 +114,10 @@ function defaultConverter(value: any, pa return "@ref-" + id; else { seen.push(v); - return v; + return v.toString() as string; } } else { - return v; + return isNull(v) ? "" : v.toString(); } }, 2); } else if (isNull(value)) { @@ -124,7 +125,7 @@ function defaultConverter(value: any, pa } else if (value instanceof Date) { return value.toISOString(); } else { - return value.toString(); + return value.toString() as string; } } @@ -136,7 +137,7 @@ export class Formatter { this._converters.push(defaultConverter); } - convert(value: any, pattern: string) { + convert(value: any, pattern?: string) { for (const c of this._converters) { const res = c(value, pattern); if (!isNull(res)) @@ -149,7 +150,7 @@ export class Formatter { const template = compile(msg); return template((name, pattern) => { - const value = args[name]; + const value = get(name, args); return !isNull(value) ? this.convert(value, pattern) : ""; }); @@ -159,7 +160,7 @@ export class Formatter { const template = compile(msg); return (...args: any[]) => { return template((name, pattern) => { - const value = args[name]; + const value = get(name, args); return !isNull(value) ? this.convert(value, pattern) : ""; }); }; diff --git a/src/main/ts/text/TextWriterBase.ts b/src/main/ts/text/TextWriterBase.ts --- a/src/main/ts/text/TextWriterBase.ts +++ b/src/main/ts/text/TextWriterBase.ts @@ -3,8 +3,6 @@ import { FormatCompiler } from "./Format import { isString, argumentNotNull } from "../safe"; import { Converter } from "./Converter"; -const compiler = new FormatCompiler(); - export abstract class TextWriterBase implements TextWriter { private _converter: Converter; @@ -21,7 +19,7 @@ export abstract class TextWriterBase imp write(format: string, ...args: any[]): void; write(format: any, ...args: any[]): void { if (args.length) { - const compiled = compiler.compile(format); + const compiled = FormatCompiler.compile(format); compiled(this, args); } else { this.writeValue(format); @@ -32,11 +30,11 @@ export abstract class TextWriterBase imp writeLine(format: string, ...args: any[]): void; writeLine(): void { if (arguments.length) - this.write.apply(this, arguments); + this.write.apply(this, arguments); this.writeNewLine(); } - writeValue(value: any, spec?: string): void { + writeValue(value: any, spec?: string) { this.writeText( isString(value) ? value : @@ -44,5 +42,5 @@ export abstract class TextWriterBase imp ); } - abstract writeText(text: string); + abstract writeText(text: string): void; } diff --git a/src/test/ts/mock/Bar.ts b/src/test/ts/mock/Bar.ts --- a/src/test/ts/mock/Bar.ts +++ b/src/test/ts/mock/Bar.ts @@ -1,28 +1,28 @@ import { Foo } from "./Foo"; -import { config } from "./config"; +// import { config } from "./config"; -const service = config.build("bar"); +// const service = config.build("bar"); -@service.consume({ - f: config.dependency("foo"), - nested: { - lazy: config.lazy("foo") - } -}) +// @service.consume({ +// f: config.dependency("foo"), +// nested: { +// lazy: config.lazy("foo") +// } +// }) export class Bar { barName = "bar"; _v: Foo | undefined; constructor(_opts: { - f: Foo; + foo: Foo; nested: { lazy: () => Foo } }) { - if (_opts && _opts.f) - this._v = _opts.f; + if (_opts && _opts.foo) + this._v = _opts.foo; } getFoo() { diff --git a/src/test/ts/mock/Box.ts b/src/test/ts/mock/Box.ts --- a/src/test/ts/mock/Box.ts +++ b/src/test/ts/mock/Box.ts @@ -1,15 +1,15 @@ -import { services } from "../di/Annotations"; -import { Bar } from "./Bar"; +// import { services } from "../di/Annotations"; +// import { Bar } from "./Bar"; // declare required dependencies -const config = services<{ - bar: Bar; -}>(); +// const config = services<{ +// bar: Bar; +// }>(); // export service descriptor -export const service = config.build>(); +// export const service = config.build>(); -@service.consume(config.dependency("bar")) +// @service.consume(config.dependency("bar")) export class Box { private _value: T | undefined; @@ -17,7 +17,7 @@ export class Box { this._value = value; } - @service.inject("bar") + // @service.inject("bar") setValue(value: T) { this._value = value; } diff --git a/src/test/ts/mock/MockActivationController.ts b/src/test/ts/mock/MockActivationController.ts --- a/src/test/ts/mock/MockActivationController.ts +++ b/src/test/ts/mock/MockActivationController.ts @@ -3,9 +3,15 @@ import { Cancellation } from "../Cancell export class MockActivationController implements IActivationController { - _active: IActivatable = null; + _active: IActivatable | null = null; + + hasActive() { + return !!this._active; + } getActive(): IActivatable { + if (!this._active) + throw new Error("No active component is set"); return this._active; } diff --git a/src/test/ts/mock/config.ts b/src/test/ts/mock/config.ts --- a/src/test/ts/mock/config.ts +++ b/src/test/ts/mock/config.ts @@ -61,8 +61,4 @@ const t = { timeout: Number }; -declare const bc: typeof Box; - -const x = new bc(); - export declare const config: ConfigBuilder; diff --git a/src/test/ts/tests/ActivatableTests.ts b/src/test/ts/tests/ActivatableTests.ts --- a/src/test/ts/tests/ActivatableTests.ts +++ b/src/test/ts/tests/ActivatableTests.ts @@ -20,8 +20,19 @@ test("controller activation", async t => const c = new MockActivationController(); t.false(a.isActive(), "the component is not active by default"); - t.assert(c.getActive() == null, "the activation controller doesn't have an active component by default"); - t.assert(a.getActivationController() == null, "the component doesn't have an activation controller by default"); + t.false(c.hasActive(), "the activation controller doesn't have an active component by default"); + try { + c.getActive(); + t.fail("Should fail when no active component is set"); + } catch (e) { + } + + t.false(a.hasActivationController(), "the component doesn't have an activation controller by default"); + try { + a.getActivationController(); + t.fail("Should fail when no activation controller is set"); + } catch (e) { + } t.comment("Active the component through the controller"); await c.activate(a); @@ -33,7 +44,7 @@ test("controller activation", async t => await c.deactivate(); t.false(a.isActive(), "The component should successfully deactivate"); - t.equal(c.getActive(), null, "The controller shouldn't point to any component"); + t.false(c.hasActive(), "The controller shouldn't point to any component"); t.equal(a.getActivationController(), c, "The componet should point to it's controller"); }); diff --git a/src/test/ts/tests/CancellationTests.ts b/src/test/ts/tests/CancellationTests.ts --- a/src/test/ts/tests/CancellationTests.ts +++ b/src/test/ts/tests/CancellationTests.ts @@ -1,10 +1,10 @@ import { Cancellation } from "../Cancellation"; -import { delay } from "../safe"; +import { delay, notImplemented } from "../safe"; import { test } from "./TestTraits"; test("standalone cancellation", async t => { - let doCancel: (e) => void; + let doCancel: (e: any) => void = notImplemented; const ct = new Cancellation(cancel => { doCancel = cancel; diff --git a/src/test/ts/tests/ContainerTests.ts b/src/test/ts/tests/ContainerTests.ts --- a/src/test/ts/tests/ContainerTests.ts +++ b/src/test/ts/tests/ContainerTests.ts @@ -6,6 +6,7 @@ import { ValueDescriptor } from "../di/V import { Foo } from "../mock/Foo"; import { Bar } from "../mock/Bar"; import { isNull } from "../safe"; +import { Descriptor } from "../di/interfaces"; test("Container register/resolve tests", async t => { const container = new Container(); @@ -13,7 +14,7 @@ test("Container register/resolve tests", const connection1 = "db://localhost"; t.throws( - () => container.register("bla-bla", "bla-bla"), + () => container.register("bla-bla", "bla-bla" as any), "Do not allow to register anything other than descriptors" ); diff --git a/src/test/ts/tests/ObservableTests.ts b/src/test/ts/tests/ObservableTests.ts --- a/src/test/ts/tests/ObservableTests.ts +++ b/src/test/ts/tests/ObservableTests.ts @@ -1,19 +1,19 @@ import { TraceSource } from "../log/TraceSource"; import { Observable } from "../Observable"; import { IObservable } from "../interfaces"; -import { delay } from "../safe"; +import { delay, fork } from "../safe"; import { test } from "./TestTraits"; const trace = TraceSource.get("ObservableTests"); test("events sequence example", async t => { - let events: IObservable; + let events: IObservable | undefined; const done = new Promise(resolve => { events = new Observable(async (notify, fail, finish) => { for (let i = 0; i < 10; i++) { - await delay(0); + await fork(); notify(i); } finish(); @@ -23,7 +23,9 @@ test("events sequence example", async t let count = 0; let complete = false; - events.on(x => count = count + x, null, () => complete = true); + if (!events) + throw new Error("events === undefined"); + events.on(x => count = count + x, undefined, () => complete = true); const first = await events.next(); @@ -37,11 +39,11 @@ test("events sequence example", async t }); test("event sequence termination", async t => { - let events: IObservable; + let events: IObservable | undefined; const done = new Promise(resolve => { events = new Observable(async (notify, fail, complete) => { - await delay(0); + await fork(); notify(1); complete(); notify(2); @@ -51,6 +53,9 @@ test("event sequence termination", async }); }); + if (!events) + throw new Error("events === undefined"); + let count = 0; events.on(() => {}, e => count++, () => count++); diff --git a/src/test/ts/tests/SafeTests.ts b/src/test/ts/tests/SafeTests.ts --- a/src/test/ts/tests/SafeTests.ts +++ b/src/test/ts/tests/SafeTests.ts @@ -1,5 +1,5 @@ import { Cancellation } from "../Cancellation"; -import { first, isPromise, firstWhere, delay, nowait } from "../safe"; +import { first, isPromise, firstWhere, delay, nowait, notImplemented } from "../safe"; import { test } from "./TestTraits"; test("await delay test", async t => { @@ -13,7 +13,7 @@ test("await delay test", async t => { t.pass("await delay"); // create cancellation token - let cancel: (e?: any) => void; + let cancel: (e?: any) => void = notImplemented; const ct = new Cancellation(c => cancel = c); // schedule delay @@ -40,14 +40,14 @@ test("await delay test", async t => { test("sequemce test", async t => { const sequence = ["a", "b", "c"]; - const empty = []; + const empty: string[] = []; // synchronous tests t.equals(first(sequence), "a", "Should return the first element"); t.equals(firstWhere(sequence, x => x === "b"), "b", "Should get the second element"); - let v: string; - let e: Error; + let v: string | undefined; + let e: Error | undefined; first(sequence, x => v = x); t.equal(v, "a", "The callback should be called for the first element"); firstWhere(sequence, x => x === "b", x => v = x); @@ -77,7 +77,7 @@ test("sequemce test", async t => { firstWhere(sequence, x => x === "z", x => v = x); }, "Should throw when the element isn't found"); - first(empty, null, x => e = x); + first(empty, undefined, x => e = x); t.true(e, "The errorback should be called for the empty sequence"); // async tests diff --git a/src/test/ts/tests/TestTraits.ts b/src/test/ts/tests/TestTraits.ts --- a/src/test/ts/tests/TestTraits.ts +++ b/src/test/ts/tests/TestTraits.ts @@ -8,7 +8,7 @@ export class TapeWriter implements IDest private readonly _tape: tape.Test; private readonly _subscriptions = new Array(); - private _destroyed; + private _destroyed = false; constructor(t: tape.Test) { argumentNotNull(t, "tape"); @@ -38,6 +38,9 @@ export class TapeWriter implements IDest } destroy() { + if (this._destroyed) + return; + this._destroyed = true; this._subscriptions.forEach(destroy); } }