From 199db79bf3336aab6263dd66f5769f0c67562c83 Mon Sep 17 00:00:00 2001 From: Chris Thielen Date: Sun, 27 Mar 2016 11:41:46 -0500 Subject: [PATCH] fix(RejectFactory): stringify rejections with circular dependency-aware stringify refactor(common): Move string functions to common/string.ts closes #2538 --- src/common/common.ts | 49 ++---------------- src/common/module.ts | 1 + src/common/strings.ts | 89 ++++++++++++++++++++++++++++++++ src/common/trace.ts | 32 ++---------- src/ng1/viewDirective.ts | 3 +- src/ng1/viewsBuilder.ts | 3 +- src/resolve/module.ts | 2 +- src/resolve/resolvable.ts | 2 +- src/resolve/resolveContext.ts | 2 +- src/resolve/resolveInjector.ts | 2 +- src/transition/rejectFactory.ts | 3 +- src/transition/transitionHook.ts | 3 +- 12 files changed, 108 insertions(+), 83 deletions(-) create mode 100644 src/common/strings.ts diff --git a/src/common/common.ts b/src/common/common.ts index 960fc06b1..21a2c8ce6 100644 --- a/src/common/common.ts +++ b/src/common/common.ts @@ -1,11 +1,11 @@ /** @module common */ /** for typedoc */ import {isFunction, isString, isArray, isRegExp, isDate} from "./predicates"; -import { all, pattern, any, not, prop, curry, val } from "./hof"; +import { all, any, not, prop, curry } from "./hof"; let angular = ( window).angular || {}; -export const fromJson = angular.fromJson || _fromJson; -export const toJson = angular.toJson || _toJson; +export const fromJson = angular.fromJson || JSON.parse.bind(JSON); +export const toJson = angular.toJson || JSON.stringify.bind(JSON); export const copy = angular.copy || _copy; export const forEach = angular.forEach || _forEach; export const extend = angular.extend || _extend; @@ -467,54 +467,11 @@ export function applyPairs(memo: TypedMap, keyValTuple: any[]) { return memo; } -export function fnToString(fn: IInjectable) { - let _fn = isArray(fn) ? fn.slice(-1)[0] : fn; - return _fn && _fn.toString() || "undefined"; -} - -/** - * Returns a string shortened to a maximum length - * - * If the string is already less than the `max` length, return the string. - * Else return the string, shortened to `max - 3` and append three dots ("..."). - * - * @param max the maximum length of the string to return - * @param str the input string - */ -export function maxLength(max: number, str: string) { - if (str.length <= max) return str; - return str.substr(0, max - 3) + "..."; -} - -/** - * Returns a string, with spaces added to the end, up to a desired str length - * - * If the string is already longer than the desired length, return the string. - * Else returns the string, with extra spaces on the end, such that it reaches `length` characters. - * - * @param length the desired length of the string to return - * @param str the input string - */ -export function padString(length: number, str: string) { - while (str.length < length) str += " "; - return str; -} - /** Get the last element of an array */ export function tail(arr: T[]): T { return arr.length && arr[arr.length - 1] || undefined; } -export const kebobString = (camelCase: string) => camelCase.replace(/([A-Z])/g, $1 => "-"+$1.toLowerCase()); - -function _toJson(obj) { - return JSON.stringify(obj); -} - -function _fromJson(json) { - return isString(json) ? JSON.parse(json) : json; -} - /** * shallow copy from src to dest * diff --git a/src/common/module.ts b/src/common/module.ts index 5b9464ca8..0dd053e26 100644 --- a/src/common/module.ts +++ b/src/common/module.ts @@ -5,4 +5,5 @@ export * from "./glob"; export * from "./hof"; export * from "./predicates"; export * from "./queue"; +export * from "./strings"; export * from "./trace"; diff --git a/src/common/strings.ts b/src/common/strings.ts new file mode 100644 index 000000000..5abd821f2 --- /dev/null +++ b/src/common/strings.ts @@ -0,0 +1,89 @@ +/** @module common */ /** */ + +import {isString, isArray, isDefined, isNull, isPromise, isInjectable, isObject} from "./predicates"; +import {TransitionRejection} from "../transition/rejectFactory"; +import {IInjectable, identity} from "./common"; +import {pattern, is, not, val, invoke} from "./hof"; +import {Transition} from "../transition/transition"; +import {Resolvable} from "../resolve/resolvable"; + +/** + * Returns a string shortened to a maximum length + * + * If the string is already less than the `max` length, return the string. + * Else return the string, shortened to `max - 3` and append three dots ("..."). + * + * @param max the maximum length of the string to return + * @param str the input string + */ +export function maxLength(max: number, str: string) { + if (str.length <= max) return str; + return str.substr(0, max - 3) + "..."; +} + +/** + * Returns a string, with spaces added to the end, up to a desired str length + * + * If the string is already longer than the desired length, return the string. + * Else returns the string, with extra spaces on the end, such that it reaches `length` characters. + * + * @param length the desired length of the string to return + * @param str the input string + */ +export function padString(length: number, str: string) { + while (str.length < length) str += " "; + return str; +} + +export const kebobString = (camelCase: string) => camelCase.replace(/([A-Z])/g, $1 => "-"+$1.toLowerCase()); + +function _toJson(obj) { + return JSON.stringify(obj); +} + +function _fromJson(json) { + return isString(json) ? JSON.parse(json) : json; +} + + +function promiseToString(p) { + if (is(TransitionRejection)(p.reason)) return p.reason.toString(); + return `Promise(${JSON.stringify(p)})`; +} + +export function functionToString(fn) { + let fnStr = fnToString(fn); + let namedFunctionMatch = fnStr.match(/^(function [^ ]+\([^)]*\))/); + return namedFunctionMatch ? namedFunctionMatch[1] : fnStr; +} + +export function fnToString(fn: IInjectable) { + let _fn = isArray(fn) ? fn.slice(-1)[0] : fn; + return _fn && _fn.toString() || "undefined"; +} + + +let stringifyPattern = pattern([ + [not(isDefined), val("undefined")], + [isNull, val("null")], + [isPromise, promiseToString], + [is(Transition), invoke("toString")], + [is(Resolvable), invoke("toString")], + [isInjectable, functionToString], + [val(true), identity] +]); + +export function stringify(o) { + var seen = []; + + function format(val) { + if (isObject(val)) { + if (seen.indexOf(val) !== -1) return '[circular ref]'; + seen.push(val); + } + return stringifyPattern(val); + } + + return JSON.stringify(o, (key, val) => format(val)).replace(/\\"/g, '"'); +} + diff --git a/src/common/trace.ts b/src/common/trace.ts index 5ec03b939..df11c6717 100644 --- a/src/common/trace.ts +++ b/src/common/trace.ts @@ -1,22 +1,9 @@ /** @module common */ /** for typedoc */ -import {fnToString, maxLength, padString, identity} from "../common/common"; -import {is, invoke, not, val, pattern, parse} from "../common/hof"; -import {isNull, isPromise, isNumber, isInjectable, isDefined} from "../common/predicates"; -import {Resolvable} from "../resolve/resolvable"; +import {parse} from "../common/hof"; +import {isNumber} from "../common/predicates"; import {Transition} from "../transition/transition"; -import {TransitionRejection} from "../transition/rejectFactory"; import {ActiveUIView, ViewConfig} from "../view/interface"; - -function promiseToString(p) { - if (is(TransitionRejection)(p.reason)) return p.reason.toString(); - return `Promise(${JSON.stringify(p)})`; -} - -function functionToString(fn) { - let fnStr = fnToString(fn); - let namedFunctionMatch = fnStr.match(/^(function [^ ]+\([^)]*\))/); - return namedFunctionMatch ? namedFunctionMatch[1] : fnStr; -} +import {stringify, functionToString, maxLength, padString} from "./strings"; function uiViewString (viewData) { if (!viewData) return 'ui-view (defunct)'; @@ -30,19 +17,6 @@ function normalizedCat(input: Category): string { return isNumber(input) ? Category[input] : Category[Category[input]]; } -function stringify(o) { - let format = pattern([ - [not(isDefined), val("undefined")], - [isNull, val("null")], - [isPromise, promiseToString], - [is(Transition), invoke("toString")], - [is(Resolvable), invoke("toString")], - [isInjectable, functionToString], - [val(true), identity] - ]); - - return JSON.stringify(o, (key, val) => format(val)).replace(/\\"/g, '"'); -} export enum Category { RESOLVE, TRANSITION, HOOK, INVOKE, UIVIEW, VIEWCONFIG diff --git a/src/ng1/viewDirective.ts b/src/ng1/viewDirective.ts index 3c5b1c9e4..19d44139f 100644 --- a/src/ng1/viewDirective.ts +++ b/src/ng1/viewDirective.ts @@ -1,6 +1,6 @@ /** @module view */ /** for typedoc */ "use strict"; -import {extend, map, unnestR, filter, kebobString} from "../common/common"; +import {extend, map, unnestR, filter} from "../common/common"; import {isDefined, isFunction} from "../common/predicates"; import {trace} from "../common/trace"; import {ActiveUIView} from "../view/interface"; @@ -12,6 +12,7 @@ import {ResolveContext} from "../resolve/resolveContext"; import {Transition} from "../transition/transition"; import {Node} from "../path/node"; import {Param} from "../params/param"; +import {kebobString} from "../common/strings"; export type UIViewData = { $cfg: Ng1ViewConfig; diff --git a/src/ng1/viewsBuilder.ts b/src/ng1/viewsBuilder.ts index c26d81055..406b87b0a 100644 --- a/src/ng1/viewsBuilder.ts +++ b/src/ng1/viewsBuilder.ts @@ -1,6 +1,7 @@ /** @module ng1 */ /** */ import {State} from "../state/stateObject"; -import {pick, forEach, anyTrueR, unnestR, kebobString} from "../common/common"; +import {pick, forEach, anyTrueR, unnestR} from "../common/common"; +import {kebobString} from "../common/strings"; import {ViewConfig, ViewContext} from "../view/interface"; import {Ng1ViewDeclaration} from "./interface"; import {ViewService} from "../view/view"; diff --git a/src/resolve/module.ts b/src/resolve/module.ts index dd53215eb..61b4d4313 100644 --- a/src/resolve/module.ts +++ b/src/resolve/module.ts @@ -1,4 +1,4 @@ -/** @module path */ /** for typedoc */ +/** @module resolve */ /** for typedoc */ export * from "./interface"; export * from "./resolvable"; export * from "./resolveContext"; diff --git a/src/resolve/resolvable.ts b/src/resolve/resolvable.ts index 3895ee009..9e1bb64da 100644 --- a/src/resolve/resolvable.ts +++ b/src/resolve/resolvable.ts @@ -1,4 +1,4 @@ -/** @module path */ /** for typedoc */ +/** @module resolve */ /** for typedoc */ import {extend, pick, map, filter} from "../common/common"; import {not} from "../common/hof"; import {isInjectable} from "../common/predicates"; diff --git a/src/resolve/resolveContext.ts b/src/resolve/resolveContext.ts index 14dd59a50..e912f74e8 100644 --- a/src/resolve/resolveContext.ts +++ b/src/resolve/resolveContext.ts @@ -1,4 +1,4 @@ -/** @module path */ /** for typedoc */ +/** @module resolve */ /** for typedoc */ import {IInjectable, find, filter, map, tail, defaults, extend, pick, omit} from "../common/common"; import {prop, propEq} from "../common/hof"; import {isString, isObject} from "../common/predicates"; diff --git a/src/resolve/resolveInjector.ts b/src/resolve/resolveInjector.ts index b04bf7fad..2cf8f8ca7 100644 --- a/src/resolve/resolveInjector.ts +++ b/src/resolve/resolveInjector.ts @@ -1,4 +1,4 @@ -/** @module path */ /** for typedoc */ +/** @module resolve */ /** for typedoc */ import {map} from "../common/common"; import {Resolvable} from "./resolvable"; diff --git a/src/transition/rejectFactory.ts b/src/transition/rejectFactory.ts index 8c4227ed4..8106964df 100644 --- a/src/transition/rejectFactory.ts +++ b/src/transition/rejectFactory.ts @@ -2,6 +2,7 @@ "use strict"; import {extend} from "../common/common"; import {services} from "../common/coreservices"; +import {stringify} from "../common/strings"; export enum RejectType { SUPERSEDED = 2, ABORTED = 3, INVALID = 4, IGNORED = 5 @@ -22,7 +23,7 @@ export class TransitionRejection { } toString() { - const detailString = d => d && d.toString !== Object.prototype.toString ? d.toString() : JSON.stringify(d); + const detailString = d => d && d.toString !== Object.prototype.toString ? d.toString() : stringify(d); let type = this.type, message = this.message, detail = detailString(this.detail); return `TransitionRejection(type: ${type}, message: ${message}, detail: ${detail})`; } diff --git a/src/transition/transitionHook.ts b/src/transition/transitionHook.ts index b34329564..31c69a0fd 100644 --- a/src/transition/transitionHook.ts +++ b/src/transition/transitionHook.ts @@ -1,6 +1,7 @@ /** @module transition */ /** for typedoc */ import {TransitionHookOptions} from "./interface"; -import {IInjectable, defaults, extend, noop, fnToString, maxLength, Predicate} from "../common/common"; +import {IInjectable, defaults, extend, noop, Predicate} from "../common/common"; +import {fnToString, maxLength} from "../common/strings"; import {isDefined, isPromise } from "../common/predicates"; import {not, pattern, val, eq, is, parse } from "../common/hof"; import {trace} from "../common/trace";