Skip to content

Commit

Permalink
fix: clean up some type issues (#173)
Browse files Browse the repository at this point in the history
update to eslint-plugin-harmony 7.0.1.
Use type-check lint
  • Loading branch information
unional authored May 13, 2022
1 parent bd97d1a commit 3c0271b
Show file tree
Hide file tree
Showing 15 changed files with 159 additions and 143 deletions.
5 changes: 1 addition & 4 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"extends": "plugin:harmony/latest",
"overrides": [
{
"extends": "plugin:harmony/ts-recommended",
"extends": "plugin:harmony/ts-recommended-type-check",
"files": [
"*.ts",
"*.tsx"
Expand All @@ -16,9 +16,6 @@
"project": "tsconfig.eslint.json"
},
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/indent": "off",
"no-console": "off",
"no-use-before-define": "off"
}
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"assertron": "^9.0.4",
"dependency-check": "^4.1.0",
"eslint": "^8.11.0",
"eslint-plugin-harmony": "^7.0.0",
"eslint-plugin-harmony": "^7.0.1",
"husky": "^8.0.0",
"jest": "^28.0.0",
"jest-progress-tracker": "^3.0.3",
Expand Down
2 changes: 1 addition & 1 deletion ts/array/drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,5 @@ export type DropUndefined<A extends Array<any>> = DropMatch<A, undefined>
* drop a particular value from an array
*/
export function drop<A extends Readonly<Array<any>>, C>(array: A, value: C): DropMatch<A, C> {
return array.filter(v => v !== value) as any
return array.filter(v => v !== value) as DropMatch<A, C>
}
2 changes: 1 addition & 1 deletion ts/functional/compose.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test('works with endofunctors: +2 *3', () => {
})

test('cross types', () => {
const n2s = (n: number) => '' + n
const n2s = (n: number) => String(n)
const len1 = (s: string) => s.length === 1

const isSingleDigit = compose(n2s, len1)
Expand Down
3 changes: 2 additions & 1 deletion ts/functional/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ import { AnyFunction } from '../function/AnyFunction'
* compose functions
*/
export function compose<FS extends AnyFunction[]>(...fns: FS): (...args: Parameters<Head<FS>>) => ReturnType<Last<FS>> {
return (...args: any[]) => fns.reduce((args, fn) => [fn(...args)], args)[0] as any
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return (...args: any[]) => fns.reduce((args, fn) => [fn(...args)], args)[0]
}
5 changes: 3 additions & 2 deletions ts/object/record.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { KeyTypes } from 'type-plus'
import { Widen } from '../utils'
import { AnyRecord } from './AnyRecord'

/**
* Creates a `Record<Key, Value>`
*/
export function record<K extends KeyTypes, V>(value?: Record<K, V>): Record<Widen<K>, V> {
const r = Object.create(null)
return value ? Object.assign(r, value) : r
const r = Object.create(null) as AnyRecord
return (value ? Object.assign(r, value) : r) as Record<Widen<K>, V>
}
3 changes: 2 additions & 1 deletion ts/object/split.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { AnyRecord } from './AnyRecord'
import { Omit } from './omit'
import { reduceByKey } from './reduceKey'
Expand Down Expand Up @@ -195,7 +196,7 @@ export function split(target: AnyRecord, ...splitters: AnyRecord[]): AnyRecord[]
const keyMap: AnyRecord = {}
const s = splitters.map(s => reduceByKey(
s,
(p, k) => (keyMap[k] = true, p[k] = target[k] ?? s[k], p),
(p, k) => (keyMap[k] = true, p[k] = target[k] ?? s[k], p),
{} as AnyRecord))
const r = reduceByKey(target, (p, k) => keyMap[k] ? p : (p[k] = target[k], p), {} as AnyRecord)
return [...s, r]
Expand Down
4 changes: 3 additions & 1 deletion ts/type-checker/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"rules": {
"@typescript-eslint/ban-types": "off"
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-return": "off"
}
}
2 changes: 1 addition & 1 deletion ts/type-checker/Boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ export const BooleanSpec: TypeSpec<Type<'boolean', boolean>> = {
return value === actual ? { type: 'boolean', value } : { type: 'boolean', value, fail: true }
}
},
toNative(value: any): true { return value as any }
toNative(value: any): true { return value }
}
4 changes: 3 additions & 1 deletion ts/types/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"rules": {
"@typescript-eslint/ban-types": "off"
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-return": "off"
}
}
20 changes: 10 additions & 10 deletions ts/types/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ export function analyzeInternal(options: analyze.Options, type: AllType, actual:
return typeof actual === t ? ok(type) : fail(t)
case 'null':
return actual === null ? ok(type) : fail(t)
case 'boolean': return analyzeBoolean(type as Boolean, actual)
case 'number': return analyzeType(number, type as Number, actual)
case 'string': return analyzeType(string, type as String, actual)
case 'boolean': return analyzeBoolean(type, actual)
case 'number': return analyzeType(number, type, actual)
case 'string': return analyzeType(string, type, actual)
// case 'bigint': return analyzeType(bigint, type as BigInt, actual)
case 'object': return analyzeObject(options, type as ObjectType, actual)
case 'record': return analyzeRecord(options, type as RecordType, actual)
case 'array': return analyzeArray(options, type as ArrayType, actual)
case 'tuple': return analyzeTuple(options, type as Tuple, actual)
case 'union': return analyzeUnion(options, type as Union, actual)
case 'object': return analyzeObject(options, type, actual)
case 'record': return analyzeRecord(options, type, actual)
case 'array': return analyzeArray(options, type, actual)
case 'tuple': return analyzeTuple(options, type, actual)
case 'union': return analyzeUnion(options, type, actual)
}
}

Expand All @@ -63,7 +63,7 @@ function analyzeType(
type: Type<any, any>,
actual: unknown
) {
const value = type['value']
const value = type.value
return typeof actual === baseType['type'] && (type === baseType || actual === value)
? ok(type)
: fail(type['type'], type['value'])
Expand All @@ -80,7 +80,7 @@ function analyzeArray(options: analyze.Options, type: ArrayType<AllType>, actual
if (!Array.isArray(actual)) return fail('array', subType ? ok(subType) : undefined)
if (subType === undefined) return ok(type)

const r = actual.reduce((p, a, i) => {
const r = actual.reduce((p: { keys: number[], actual: any[], value: any }, a, i) => {
const r = analyzeInternal(options, subType, a)
if (r.fail) {
p.value = r.value
Expand Down
11 changes: 11 additions & 0 deletions ts/types/getPlainAnalysisReport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,17 @@ describe('tuple', () => {
].join('\n')
)
})
test('strict with more than 2 extra', () => {
assertReportEquals(
strict,
T.tuple.create(T.null, T.number),
[null, 1, 'a', false, true],
[
`subject expects to be strictly [null,number] but is actually [null, 1, 'a', false, true]`,
`indices 2..4 should not contain any value`
].join('\n')
)
})
})
describe('object', () => {
test('not object', () => {
Expand Down
230 changes: 115 additions & 115 deletions ts/types/getPlainAnalysisReport.ts
Original file line number Diff line number Diff line change
@@ -1,115 +1,115 @@
import { tersify } from 'tersify'
import { reduceByKey } from '../object'
import { AllType } from './AllType'
import { analyze } from './analyze'

export function getPlainAnalysisReport(analysisResult: analyze.Result) {
if (!analysisResult.analysis.fail) {
return `The subject${analysisResult.options.strict ? ' strictly' : ''} satisfies type ${formatAnalysis(analysisResult.analysis)}`
}

return toViolations(analysisResult.options, [], analysisResult.actual, analysisResult.analysis).join('\n')
}

function formatAnalysis(a: AllType.Analysis) {
return a.type
}

function toViolations(options: analyze.Options, path: Array<string | number>, actual: any, analysis: AllType.Analysis): string[] {
const clause = options.strict ? `expects to be strictly` : `expects to be`
const violations = [`${formatPath(path)} ${clause} ${formatType(analysis)} but is actually ${tersify(actual)}`]

switch (analysis.type) {
case 'tuple': {
if (options.strict && analysis.value!.length < actual.length) {
const index = actual.length - analysis.value!.length === 1 ? 'index' : 'indices'
violations.push(`${index} ${range(analysis.value!.length, actual.length)} should not contain any value`)
}
const v2 = analysis.value!.reduce((p, a, i) => {
if (a.fail) p.push(...toViolations(options, [...path, i], actual[i], a))
return p
}, [] as string[])
if (v2.length > 0) violations.push(...v2)
break
}
case 'object':
if (analysis.value) {
const typeKeys = Object.keys(analysis.value)
const actualKeys = Object.keys(actual)
if (options.strict) {
const extraKeys = actualKeys.filter(k => typeKeys.indexOf(k) === -1)
const index = extraKeys.length === 1 ? 'property' : 'properties'
violations.push(`${index} ${extraKeys.join(',')} should not contain any value`)
}
const propViolations = typeKeys.reduce((p, k) => {
const a = analysis.value![k]
if (a.fail) p.push(...toViolations(options, [...path, k], actual[k], a))
return p
}, [] as string[])
if (propViolations.length > 0) violations.push(...propViolations)
}
break
}
return violations
}

function formatPath(path: Array<string | number>) {
return path.length === 0 ? 'subject' : `subject${path.map(p => toAccessor(p)).join('')}`
}

function toAccessor(p: string | number) {
if (typeof p === 'number') return `[${p}]`
if (p.indexOf('-') >= 0) return `['${p}']`
return `.${p}`
}

function toProp(p: string) {
return p.indexOf('-') >= 0 ? `'${p}'` : p
}

function formatType(e: AllType.Analysis): string {
switch (e.type) {
case 'undefined':
case 'null':
case 'symbol':
// case 'never':
return e.type
case 'boolean':
case 'number':
// case 'bigint':
return e.value === undefined ? e.type : String(e.value)
case 'string':
return e.value === undefined ? e.type : `'${e.value}'`
case 'array':
return e.value === undefined ? 'Array<any>' : `Array<${formatType(e.value as any)}>`
case 'tuple':
return `[${formatExpectations(e.value as AllType.Analysis[])}]`
case 'object':
return e.value === undefined ? e.type
: `{ ${reduceByKey(
e.value as Record<string, any>,
(p, k) => {
p.push(`${toProp(k)}: ${formatType((e.value as any)[k] as any)}`)
return p
},
[] as string[]
).join(', ')} }`
case 'record':
return `Record<string, ${formatType(e.value as any)}>`
case 'union':
return `(${formatExpectations(e.value as AllType.Analysis[], ' | ')})`
// istanbul ignore next
default:
return `report not expected: ${e.value}`
}
}

function formatExpectations(es: AllType.Analysis[], sep = ',') {
return es.map(formatType).join(sep)
}

function range(start: number, end: number) {
const r: number[] = []
while (start < end) r.push(start++)
return r
}
import { tersify } from 'tersify'
import { reduceByKey } from '../object'
import { AllType } from './AllType'
import { analyze } from './analyze'

export function getPlainAnalysisReport(analysisResult: analyze.Result) {
if (!analysisResult.analysis.fail) {
return `The subject${analysisResult.options.strict ? ' strictly' : ''} satisfies type ${formatAnalysis(analysisResult.analysis)}`
}

return toViolations(analysisResult.options, [], analysisResult.actual, analysisResult.analysis).join('\n')
}

function formatAnalysis(a: AllType.Analysis) {
return a.type
}

function toViolations(options: analyze.Options, path: Array<string | number>, actual: any, analysis: AllType.Analysis): string[] {
const clause = options.strict ? `expects to be strictly` : `expects to be`
const violations = [`${formatPath(path)} ${clause} ${formatType(analysis)} but is actually ${tersify(actual)}`]

switch (analysis.type) {
case 'tuple': {
if (options.strict && analysis.value!.length < actual.length) {
violations.push(`${range(analysis.value!.length, actual.length)} should not contain any value`)
}
const v2 = analysis.value!.reduce((p: string[], a, i) => {
if (a.fail) p.push(...toViolations(options, [...path, i], actual[i], a))
return p
}, [])
if (v2.length > 0) violations.push(...v2)
break
}
case 'object':
if (analysis.value) {
const typeKeys = Object.keys(analysis.value)
const actualKeys = Object.keys(actual)
if (options.strict) {
const extraKeys = actualKeys.filter(k => typeKeys.indexOf(k) === -1)
const index = extraKeys.length === 1 ? 'property' : 'properties'
violations.push(`${index} ${extraKeys.join(',')} should not contain any value`)
}
const propViolations = typeKeys.reduce((p, k) => {
const a = analysis.value![k]
if (a.fail) p.push(...toViolations(options, [...path, k], actual[k], a))
return p
}, [] as string[])
if (propViolations.length > 0) violations.push(...propViolations)
}
break
}
return violations
}

function formatPath(path: Array<string | number>) {
return path.length === 0 ? 'subject' : `subject${path.map(p => toAccessor(p)).join('')}`
}

function toAccessor(p: string | number) {
if (typeof p === 'number') return `[${p}]`
if (p.indexOf('-') >= 0) return `['${p}']`
return `.${p}`
}

function toProp(p: string) {
return p.indexOf('-') >= 0 ? `'${p}'` : p
}

function formatType(e: AllType.Analysis): string {
switch (e.type) {
case 'undefined':
case 'null':
case 'symbol':
// case 'never':
return e.type
case 'boolean':
case 'number':
// case 'bigint':
return e.value === undefined ? e.type : String(e.value)
case 'string':
return e.value === undefined ? e.type : `'${e.value}'`
case 'array':
return e.value === undefined ? 'Array<any>' : `Array<${formatType(e.value)}>`
case 'tuple':
return `[${formatExpectations(e.value as AllType.Analysis[])}]`
case 'object':
return e.value === undefined ? e.type
: `{ ${reduceByKey(
e.value,
(p, k) => {
p.push(`${toProp(k)}: ${formatType((e.value as any)[k])}`)
return p
},
[] as string[]
).join(', ')} }`
case 'record':
return `Record<string, ${formatType(e.value)}>`
case 'union':
return `(${formatExpectations(e.value as AllType.Analysis[], ' | ')})`
// istanbul ignore next
default:
return `report not expected: ${e.type}`
}
}

function formatExpectations(es: AllType.Analysis[], sep = ',') {
return es.map(formatType).join(sep)
}

function range(start: number, end: number) {
const diff = end - start
if (diff === 1) return `index ${start}`
if (diff === 2) return `indices ${start},${end - 1}`
return `indices ${start}..${end - 1}`
}
Loading

0 comments on commit 3c0271b

Please sign in to comment.