@code-to-json/utils
Advanced tools
Comparing version 0.2.0 to 0.4.2
125
CHANGELOG.md
@@ -6,2 +6,127 @@ # Change Log | ||
## 0.4.2 (2018-10-29) | ||
**Note:** Version bump only for package @code-to-json/utils | ||
## 0.4.1 (2018-10-29) | ||
**Note:** Version bump only for package @code-to-json/utils | ||
# 0.4.0 (2018-10-28) | ||
### Bug Fixes | ||
* add licenses ([6a04ef2](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/6a04ef2)) | ||
* add test-helpers devDep to all packages ([e3f69da](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/e3f69da)) | ||
* better typing on processing queue ([4ff3ad0](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/4ff3ad0)) | ||
* lock packages ([6cb8cbb](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/6cb8cbb)) | ||
* refactor registry and refs ([ca9eaa8](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/ca9eaa8)) | ||
* references are now tuples ([46ae8bd](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/46ae8bd)) | ||
* source files as top-level entity ([3afb975](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/3afb975)) | ||
### Features | ||
* basic walking functionality ([584f23e](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/584f23e)) | ||
* human-readable types in type references ([7b2abcb](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/7b2abcb)) | ||
* queue/flush graph traversal ([9cfed8f](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/9cfed8f)) | ||
* rebuild cli tool to support globs, walk symbols instead of nodes ([3c9539d](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/3c9539d)) | ||
* serialize non-exports ([613091d](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/613091d)) | ||
## [0.3.7](https://github.com/mike-north/code-to-json/tree/master/packages/utils/compare/@code-to-json/utils@0.3.6...@code-to-json/utils@0.3.7) (2018-10-27) | ||
**Note:** Version bump only for package @code-to-json/utils | ||
## [0.3.6](https://github.com/mike-north/code-to-json/tree/master/packages/utils/compare/@code-to-json/utils@0.3.5...@code-to-json/utils@0.3.6) (2018-10-26) | ||
**Note:** Version bump only for package @code-to-json/utils | ||
## [0.3.5](https://github.com/mike-north/code-to-json/tree/master/packages/utils/compare/@code-to-json/utils@0.3.4...@code-to-json/utils@0.3.5) (2018-10-26) | ||
### Bug Fixes | ||
* add test-helpers devDep to all packages ([637a178](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/637a178)) | ||
## [0.3.4](https://github.com/mike-north/code-to-json/tree/master/packages/utils/compare/@code-to-json/utils@0.3.3...@code-to-json/utils@0.3.4) (2018-10-25) | ||
### Bug Fixes | ||
* refactor registry and refs ([3a3bec9](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/3a3bec9)) | ||
* references are now tuples ([81d9cc3](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/81d9cc3)) | ||
## [0.3.3](https://github.com/mike-north/code-to-json/tree/master/packages/utils/compare/@code-to-json/utils@0.3.2...@code-to-json/utils@0.3.3) (2018-10-21) | ||
**Note:** Version bump only for package @code-to-json/utils | ||
## [0.3.2](https://github.com/mike-north/code-to-json/tree/master/packages/utils/compare/@code-to-json/utils@0.3.1...@code-to-json/utils@0.3.2) (2018-10-21) | ||
**Note:** Version bump only for package @code-to-json/utils | ||
## [0.3.1](https://github.com/mike-north/code-to-json/tree/master/packages/utils/compare/@code-to-json/utils@0.3.0...@code-to-json/utils@0.3.1) (2018-10-21) | ||
### Bug Fixes | ||
* source files as top-level entity ([ef1a3d5](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/ef1a3d5)) | ||
# [0.3.0](https://github.com/mike-north/code-to-json/tree/master/packages/utils/compare/@code-to-json/utils@0.2.0...@code-to-json/utils@0.3.0) (2018-10-20) | ||
### Bug Fixes | ||
* better typing on processing queue ([dbce5b2](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/dbce5b2)) | ||
### Features | ||
* human-readable types in type references ([6525b93](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/6525b93)) | ||
* serialize non-exports ([f1f9355](https://github.com/mike-north/code-to-json/tree/master/packages/utils/commit/f1f9355)) | ||
# [0.2.0](https://github.com/mike-north/code-to-json/tree/master/packages/utils/compare/@code-to-json/utils@0.1.5...@code-to-json/utils@0.2.0) (2018-10-19) | ||
@@ -8,0 +133,0 @@ |
{ | ||
"name": "@code-to-json/utils", | ||
"version": "0.2.0", | ||
"version": "0.4.2", | ||
"description": "", | ||
"main": "lib/index.js", | ||
"typings": "lib/index.d.ts", | ||
"main": "lib/src/index.js", | ||
"typings": "lib/src/index.d.ts", | ||
"license": "BSD-2-Clause", | ||
@@ -12,3 +12,7 @@ "author": "Mike North <michael.l.north@gmail.com> (https://mike.works)", | ||
"lint": "tslint --project .", | ||
"test": "echo \"Error: no test specified\" && exit 0", | ||
"test": "mocha", | ||
"test:coverage": "nyc npm test", | ||
"clean": "rm -rf ./lib", | ||
"build": "tsc -b .", | ||
"rebuild": "npm run clean & npm run build", | ||
"prepublishOnly": "tsc -b ." | ||
@@ -18,6 +22,43 @@ }, | ||
"devDependencies": { | ||
"@code-to-json/test-helpers": "^0.0.8", | ||
"nyc": "^13.1.0", | ||
"tslint": "^5.11.0", | ||
"typescript": "^3.1.2" | ||
}, | ||
"gitHead": "bb91606c037b670021c8ef3079cd83bb62a5ef16" | ||
"nyc": { | ||
"watermarks": { | ||
"lines": [ | ||
80, | ||
95 | ||
], | ||
"functions": [ | ||
80, | ||
95 | ||
], | ||
"branches": [ | ||
80, | ||
95 | ||
], | ||
"statements": [ | ||
80, | ||
95 | ||
] | ||
}, | ||
"require": [ | ||
"ts-node/register", | ||
"source-map-support/register" | ||
], | ||
"extension": [ | ||
".ts" | ||
], | ||
"include": [ | ||
"src" | ||
], | ||
"reporter": [ | ||
"lcov", | ||
"json", | ||
"text-summary" | ||
] | ||
}, | ||
"gitHead": "33430b512f475a40ef545ddcd79a48cc7767dec5" | ||
} |
@@ -1,2 +0,10 @@ | ||
function get(obj: any, propname: string) { | ||
import * as ts from 'typescript'; | ||
/** | ||
* Get a property from a target object | ||
* @param obj target | ||
* @param propname property name | ||
*/ | ||
function get<O extends object, K extends keyof O>(obj: O, propname: K): O[K]; | ||
function get(obj: any, propname: string): any; | ||
function get(obj: any, propname: string): any { | ||
if (obj && typeof obj === 'object') { | ||
@@ -8,4 +16,29 @@ return obj[propname]; | ||
} | ||
// i | ||
/** | ||
* Returns true if the passed value is null or undefined. This avoids errors | ||
* from JSLint complaining about use of ==, which can be technically | ||
* confusing. | ||
* ```ts | ||
* isNone(); // true | ||
* isNone(null); // true | ||
* isNone(undefined); // true | ||
* isNone(''); // false | ||
* isNone([]); // false | ||
* isNone(function() {}); // false | ||
* ``` | ||
* @note: copied from https://github.com/emberjs/ember.js/blob/5a8873bee19774a55fd0abfdcc7279f3efc768cd/packages/ember-metal/lib/is_none.ts#L25-L27 | ||
*/ | ||
export function isNone(obj: any): obj is null | undefined { | ||
return obj === null || obj === undefined; | ||
} | ||
export function isEmpty(obj: any): boolean { | ||
/** | ||
* Verifies that a value is null or undefined, an empty string, or an empty array. | ||
* Constrains the rules on isNone by returning true for empty strings and empty arrays. | ||
* If the value is an object with a size property of type number, it is used to check emptiness. | ||
* @param obj | ||
*/ | ||
// tslint:disable-next-line:max-union-size | ||
export function isEmpty(obj: any): obj is null | undefined | 0 | { size: 0 } | [] | '' { | ||
const none = obj === null || obj === undefined; | ||
@@ -24,5 +57,9 @@ if (none) { | ||
const size = get(obj, 'size'); | ||
const length = get(obj, 'length'); | ||
if (typeof size === 'number') { | ||
return !size; | ||
} | ||
if (typeof length === 'number') { | ||
return !length; | ||
} | ||
} | ||
@@ -34,18 +71,32 @@ | ||
if (objectType === 'object') { | ||
const length = get(obj, 'length'); | ||
if (typeof length === 'number') { | ||
return !length; | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Check a value for blankness | ||
* @param obj value to check for blankness | ||
* @see isPresent | ||
*/ | ||
export function isBlank(obj: any): boolean { | ||
return isEmpty(obj) || (typeof obj === 'string' && /\S/.test(obj) === false); | ||
} | ||
export function isPresent(obj: object): any { | ||
/** | ||
* Check a value for non-blankness | ||
* @param obj object to check for presence | ||
* @see isBlank | ||
*/ | ||
export function isPresent(obj: any): boolean { | ||
return !isBlank(obj); | ||
} | ||
/** | ||
* Check whether a declaration is visible outside its respective file | ||
* @param declaration Declaration to check | ||
*/ | ||
export function isDeclarationExported(declaration: ts.Declaration): boolean { | ||
return ( | ||
// tslint:disable-next-line:no-bitwise | ||
(ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Export) !== 0 || | ||
(!!declaration.parent && declaration.parent.kind === ts.SyntaxKind.SourceFile) | ||
); | ||
} |
const BASE_MESSAGE = 'Reached a case that should be unreachable'; | ||
/** | ||
* Build an error message string that starts with `BASE_MESSAGE` | ||
* @param message error message suffix | ||
*/ | ||
function createMessage(message?: string): string { | ||
@@ -11,3 +15,6 @@ const a: string[] = [BASE_MESSAGE]; | ||
export default class UnreachableError extends Error { | ||
/** | ||
* An error that's associated with un-reachable code | ||
*/ | ||
export class UnreachableError extends Error { | ||
constructor(_arg: never, message?: string) { | ||
@@ -14,0 +21,0 @@ super(createMessage(message)); |
@@ -1,2 +0,32 @@ | ||
export * from './array'; | ||
export * from './ts/node'; | ||
export function isObject<T extends object>(val?: T): val is T { | ||
return typeof val !== 'undefined'; | ||
} | ||
/** | ||
* Check to see whether a value is an array | ||
* @param value value to check | ||
*/ | ||
export function isArray(value: any): value is any[]; | ||
export function isArray<T>(value?: T[]): value is T[]; | ||
export function isArray(value: any): value is any[] { | ||
return value instanceof Array; | ||
} | ||
/** | ||
* Check to see if a value is a homogenous array | ||
* @param value value to check | ||
* @param validator validator to apply to each member of the collection | ||
*/ | ||
export function isHomogenousArray<T>(value: any, validator: (v: any) => v is T): value is T[] { | ||
if (!isArray(value)) { | ||
return false; | ||
} | ||
for (const v of value) { | ||
if (!validator(v)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} |
@@ -1,2 +0,16 @@ | ||
import * as ts from 'typescript'; | ||
import { | ||
Declaration, | ||
isClassLike, | ||
isFunctionLike, | ||
isObjectLiteralElement, | ||
isParameter, | ||
isPropertyDeclaration, | ||
isTypeParameterDeclaration, | ||
isVariableDeclaration, | ||
NamedDeclaration, | ||
Node, | ||
Symbol as Sym, | ||
SyntaxKind, | ||
Type | ||
} from 'typescript'; | ||
@@ -6,85 +20,91 @@ /** | ||
* | ||
* Based on ts.isDeclarationKind() from the compiler. | ||
* Based on isDeclarationKind() from the compiler. | ||
* https://github.com/Microsoft/TypeScript/blob/v3.0.3/src/compiler/utilities.ts#L6382 | ||
*/ | ||
function isDeclarationKind(kind: ts.SyntaxKind): boolean { | ||
function isDeclarationKind(kind: SyntaxKind): boolean { | ||
return ( | ||
kind === ts.SyntaxKind.SourceFile || | ||
kind === ts.SyntaxKind.ArrowFunction || | ||
kind === ts.SyntaxKind.BindingElement || | ||
kind === ts.SyntaxKind.ClassDeclaration || | ||
kind === ts.SyntaxKind.ClassExpression || | ||
kind === ts.SyntaxKind.Constructor || | ||
kind === ts.SyntaxKind.EnumDeclaration || | ||
kind === ts.SyntaxKind.EnumMember || | ||
kind === ts.SyntaxKind.ExportSpecifier || | ||
kind === ts.SyntaxKind.FunctionDeclaration || | ||
kind === ts.SyntaxKind.FunctionExpression || | ||
kind === ts.SyntaxKind.GetAccessor || | ||
kind === ts.SyntaxKind.ImportClause || | ||
kind === ts.SyntaxKind.ImportEqualsDeclaration || | ||
kind === ts.SyntaxKind.ImportSpecifier || | ||
kind === ts.SyntaxKind.InterfaceDeclaration || | ||
kind === ts.SyntaxKind.JsxAttribute || | ||
kind === ts.SyntaxKind.MethodDeclaration || | ||
kind === ts.SyntaxKind.MethodSignature || | ||
kind === ts.SyntaxKind.ModuleDeclaration || | ||
kind === ts.SyntaxKind.NamespaceExportDeclaration || | ||
kind === ts.SyntaxKind.NamespaceImport || | ||
kind === ts.SyntaxKind.Parameter || | ||
kind === ts.SyntaxKind.PropertyAssignment || | ||
kind === ts.SyntaxKind.PropertyDeclaration || | ||
kind === ts.SyntaxKind.PropertySignature || | ||
kind === ts.SyntaxKind.SetAccessor || | ||
kind === ts.SyntaxKind.ShorthandPropertyAssignment || | ||
kind === ts.SyntaxKind.TypeAliasDeclaration || | ||
kind === ts.SyntaxKind.TypeParameter || | ||
kind === ts.SyntaxKind.VariableDeclaration || | ||
kind === ts.SyntaxKind.JSDocTypedefTag || | ||
kind === ts.SyntaxKind.JSDocCallbackTag || | ||
kind === ts.SyntaxKind.JSDocPropertyTag | ||
kind === SyntaxKind.SourceFile || | ||
kind === SyntaxKind.ArrowFunction || | ||
kind === SyntaxKind.BindingElement || | ||
kind === SyntaxKind.ClassDeclaration || | ||
kind === SyntaxKind.ClassExpression || | ||
kind === SyntaxKind.Constructor || | ||
kind === SyntaxKind.EnumDeclaration || | ||
kind === SyntaxKind.EnumMember || | ||
kind === SyntaxKind.ExportSpecifier || | ||
kind === SyntaxKind.FunctionDeclaration || | ||
kind === SyntaxKind.FunctionExpression || | ||
kind === SyntaxKind.GetAccessor || | ||
kind === SyntaxKind.ImportClause || | ||
kind === SyntaxKind.ImportEqualsDeclaration || | ||
kind === SyntaxKind.ImportSpecifier || | ||
kind === SyntaxKind.InterfaceDeclaration || | ||
kind === SyntaxKind.JsxAttribute || | ||
kind === SyntaxKind.MethodDeclaration || | ||
kind === SyntaxKind.MethodSignature || | ||
kind === SyntaxKind.ModuleDeclaration || | ||
kind === SyntaxKind.NamespaceExportDeclaration || | ||
kind === SyntaxKind.NamespaceImport || | ||
kind === SyntaxKind.Parameter || | ||
kind === SyntaxKind.PropertyAssignment || | ||
kind === SyntaxKind.PropertyDeclaration || | ||
kind === SyntaxKind.PropertySignature || | ||
kind === SyntaxKind.SetAccessor || | ||
kind === SyntaxKind.ShorthandPropertyAssignment || | ||
kind === SyntaxKind.TypeAliasDeclaration || | ||
kind === SyntaxKind.TypeParameter || | ||
kind === SyntaxKind.VariableDeclaration || | ||
kind === SyntaxKind.JSDocTypedefTag || | ||
kind === SyntaxKind.JSDocCallbackTag || | ||
kind === SyntaxKind.JSDocPropertyTag | ||
); | ||
} | ||
export function isNamedDeclaration(node: ts.Node): node is ts.NamedDeclaration { | ||
/** | ||
* Check to see whether a value is a named declaration | ||
* @param node value to check | ||
*/ | ||
export function isNamedDeclaration(node?: Node): node is NamedDeclaration { | ||
return ( | ||
ts.isClassLike(node) || | ||
ts.isFunctionLike(node) || | ||
ts.isTypeParameterDeclaration(node) || | ||
ts.isParameter(node) || | ||
ts.isObjectLiteralElement(node) || | ||
ts.isPropertyDeclaration(node) || | ||
ts.isVariableDeclaration(node) | ||
!!node && | ||
(isClassLike(node) || | ||
isFunctionLike(node) || | ||
isTypeParameterDeclaration(node) || | ||
isParameter(node) || | ||
isObjectLiteralElement(node) || | ||
isPropertyDeclaration(node) || | ||
isVariableDeclaration(node)) | ||
); | ||
} | ||
export function isDeclaration( | ||
node: ts.Node | ts.Declaration | ||
): node is ts.Declaration { | ||
return isDeclarationKind(node.kind); | ||
/** | ||
* Check to see whether a value is a Declaration | ||
* @param node value to check | ||
*/ | ||
export function isDeclaration(node?: Node | Declaration): node is Declaration { | ||
return !!node && isDeclarationKind(node.kind); | ||
} | ||
/** True if this is visible outside this file, false otherwise */ | ||
export function isNodeExported(node: ts.Declaration): boolean { | ||
return ( | ||
// tslint:disable-next-line:no-bitwise | ||
(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0 || | ||
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile) | ||
); | ||
/** | ||
* Check to see whether a value is a Type | ||
* @param thing value to check | ||
*/ | ||
export function isType(thing?: Sym | Type | Node): thing is Type { | ||
return !!thing && !!(thing as Type).getBaseTypes && !!(thing as Type).isUnion; | ||
} | ||
export function isType( | ||
thing: ts.Symbol | ts.Declaration | ts.Type | ts.Node | ||
): thing is ts.Type { | ||
return !!(thing as ts.Type).getBaseTypes && !!(thing as ts.Type).isUnion; | ||
/** | ||
* Check to see whether a value is a Symbol | ||
* @param thing value to check | ||
*/ | ||
export function isSymbol(thing?: Sym | Type | Node): thing is Sym { | ||
return !!thing && typeof (thing as Sym).getEscapedName === 'function'; | ||
} | ||
export function isSymbol( | ||
thing: ts.Symbol | ts.Declaration | ts.Type | ts.Node | ||
): thing is ts.Symbol { | ||
return !!(thing as any).escapedName; | ||
/** | ||
* Check to see whether a value is a Node | ||
* @param thing value to check | ||
*/ | ||
export function isNode(thing?: Sym | Type | Node): thing is Node { | ||
return !!thing && typeof (thing as Node).getChildAt === 'function'; | ||
} | ||
export function isNode( | ||
thing: ts.Symbol | ts.Declaration | ts.Type | ts.Node | ||
): thing is ts.Node { | ||
return !!(thing as any).kind; | ||
} |
@@ -1,4 +0,16 @@ | ||
export * from './errors'; | ||
export * from './guards'; | ||
export * from './types'; | ||
export * from './ts'; | ||
export { UnreachableError } from './errors/unreachable'; | ||
export { | ||
isNode, | ||
isType, | ||
isSymbol, | ||
isDeclaration, | ||
isObject, | ||
isArray, | ||
isNamedDeclaration | ||
} from './guards'; | ||
export { Result, ErrorResult, SuccessResult } from './types'; | ||
export { mapUem } from './ts/underscore-escaped-map'; | ||
export { mapChildren } from './ts/node'; | ||
export { isDeclarationExported, isBlank, isPresent, isEmpty, isNone } from './checks'; | ||
export { createQueue } from './deferred-processing/queue'; | ||
export { Ref, RefFor, AnyRef, refType, refId, isRef } from './deferred-processing/ref'; |
@@ -1,3 +0,2 @@ | ||
export * from './flags'; | ||
export * from './node'; | ||
export * from './underscore-escaped-map'; |
@@ -1,9 +0,11 @@ | ||
import * as ts from 'typescript'; | ||
import { forEachChild, Node } from 'typescript'; | ||
export function mapChildren<T>( | ||
node: ts.Node, | ||
mapper: (child: ts.Node) => T | ||
): T[] { | ||
/** | ||
* Map over a TypeScript AST node's children | ||
* @param node parent node | ||
* @param mapper mapping function to apply to each child | ||
*/ | ||
export function mapChildren<T>(node: Node, mapper: (child: Node) => T): T[] { | ||
const arr: T[] = []; | ||
ts.forEachChild(node, (child: ts.Node) => { | ||
forEachChild(node, (child: Node) => { | ||
arr.push(mapper(child)); | ||
@@ -10,0 +12,0 @@ }); |
@@ -1,6 +0,11 @@ | ||
import * as ts from 'typescript'; | ||
import { __String, UnderscoreEscapedMap } from 'typescript'; | ||
/** | ||
* Map over an UnderscoreEscapedMap | ||
* @param uem UnderscoreEscapedMap to iterate over | ||
* @param callback mapping function to apply to each key-value pair | ||
*/ | ||
export function mapUem<T, S>( | ||
uem: ts.UnderscoreEscapedMap<T>, | ||
callback: ((t: T, key: ts.__String) => S) | ||
uem: UnderscoreEscapedMap<T>, | ||
callback: ((t: T, key: __String) => S) | ||
): S[] { | ||
@@ -7,0 +12,0 @@ const arr: S[] = []; |
@@ -1,9 +0,3 @@ | ||
import * as ts from 'typescript'; | ||
export type ErrorResult<E extends Error = Error> = ['error', E]; | ||
export type SuccessResult<T> = ['ok', T]; | ||
export type Result<T, E extends Error = Error> = | ||
| SuccessResult<T> | ||
| ErrorResult<E>; | ||
export type Flags = string | string[]; | ||
export type Result<T, E extends Error = Error> = SuccessResult<T> | ErrorResult<E>; |
@@ -5,7 +5,9 @@ { | ||
"outDir": "lib", | ||
"rootDir": "src", | ||
"rootDir": ".", | ||
"baseUrl": "src", | ||
"target": "es2017", | ||
"lib": ["scripthost", "es2015"] | ||
}, | ||
"include": ["src"], | ||
"exclude": ["node_modules/**/*", "lib/**/*"] | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
129269
1659
2
4
71