Skip to content

Commit

Permalink
feat: Object type and empty type is considered as any in --strict, an…
Browse files Browse the repository at this point in the history
…d can be ingored by --ignore-object and --ignore-empty-type

#108
  • Loading branch information
plantain-00 committed Feb 2, 2022
1 parent ed791de commit a290acc
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 4 deletions.
8 changes: 8 additions & 0 deletions README.md
Expand Up @@ -47,6 +47,8 @@ name | type | description
`--ignore-as-assertion` | boolean? | ignore as assertion, eg: `foo as string`(Added in `v2.16`)
`--ignore-type-assertion` | boolean? | ignore type assertion, eg: `<string>foo`(Added in `v2.16`)
`--ignore-non-null-assertion` | boolean? | ignore non-null assertion, eg: `foo!`(Added in `v2.16`)
`--ignore-object` | boolean? | `Object` type not counted as any, eg: `foo: Object`(Added in `v2.21`)
`--ignore-empty-type` | boolean? | empty type not counted as any, eg: `foo: {}`(Added in `v2.21`)
`--show-relative-path` | boolean? | show relative path in detail message(Added in `v2.17`)
`--history-file` | string? | file name where history is saved(Added in `v2.18`)
`--no-detail-when-failed` | boolean? | not show detail message when the CLI failed(Added in `v2.19`)
Expand All @@ -57,6 +59,8 @@ If the identifiers' type arguments exist and contain at least one `any`, like `a

Type assertion, like `foo as string`, `foo!`, `<string>foo` will be considered as uncovered, exclude `foo as const`, `<const>foo`, `foo as unknown`(Added in `v2.8`), and other safe type assertion powered by `isTypeAssignableTo`(Added in `v2.9`)

Object type(like `foo: Object`) and empty type(like `foo: {}`) will be considered as any(Added in `v2.21`)

Also, future minor release may introduce stricter type check in this mode, which may lower the type coverage rate

### enable cache
Expand Down Expand Up @@ -108,6 +112,8 @@ This tool will ignore the files, eg: `--ignore-files "demo1/*.ts" --ignore-files
"ignoreAsAssertion": true, // same as --ignore-as-assertion (Added in `v2.16`)
"ignoreTypeAssertion": true, // same as --ignore-type-assertion (Added in `v2.16`)
"ignoreNonNullAssertion": true, // same as --ignore-non-null-assertion (Added in `v2.16`)
"ignoreObject": true, // same as --ignore-object(Added in `v2.21`)
"ignoreEmptyType": true, // same as --ignore-empty-type(Added in `v2.21`)
"showRelativePath": true, // same as --show-relative-path (Added in `v2.17`)
"historyFile": "typecoverage.json", // same as --history-file (Added in `v2.18`)
"noDetailWhenFailed": true, // same as --no-detail-when-failed (Added in `v2.19`)
Expand Down Expand Up @@ -173,6 +179,8 @@ export interface LintOptions {
ignoreAsAssertion: boolean // Added in v2.16
ignoreTypeAssertion: boolean // Added in v2.16
ignoreNonNullAssertion: boolean // Added in v2.16
ignoreObject: boolean // Added in v2.21
ignoreEmptyType: boolean // Added in v2.21
}

export interface FileTypeCheckResult {
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/src/index.ts
Expand Up @@ -35,6 +35,8 @@ function printHelp() {
--ignore-as-assertion boolean? ignore as assertion, eg: foo as string
--ignore-type-assertion boolean? ignore type assertion, eg: <string>foo
--ignore-non-null-assertion boolean? ignore non-null assertion, eg: foo!
--ignore-object boolean? Object type not counted as any, eg: foo: Object
--ignore-empty-type boolean? empty type not counted as any, eg: foo: {}
--show-relative-path boolean? show relative path in detail message
--history-file string? file name where history is saved
--no-detail-when-failed boolean? not show detail message when the CLI failed
Expand Down Expand Up @@ -68,6 +70,8 @@ interface CliArgs extends BaseArgs {
['ignore-as-assertion']: boolean
['ignore-type-assertion']: boolean
['ignore-non-null-assertion']: boolean
['ignore-object']: boolean
['ignore-empty-type']: boolean

['history-file']: string
['no-detail-when-failed']: boolean
Expand All @@ -85,6 +89,8 @@ interface PkgArgs extends BaseArgs {
ignoreAsAssertion: boolean
ignoreTypeAssertion: boolean
ignoreNonNullAssertion: boolean
ignoreObject: boolean
ignoreEmptyType: boolean

historyFile: string
noDetailWhenFailed: boolean
Expand Down Expand Up @@ -128,6 +134,8 @@ async function executeCommandLine() {
ignoreAsAssertion,
ignoreTypeAssertion,
ignoreNonNullAssertion,
ignoreObject,
ignoreEmptyType,
showRelativePath,
historyFile,
noDetailWhenFailed,
Expand All @@ -144,6 +152,8 @@ async function executeCommandLine() {
ignoreAsAssertion: ignoreAsAssertion,
ignoreTypeAssertion,
ignoreNonNullAssertion,
ignoreObject,
ignoreEmptyType,
});

const percent = Math.floor(10000 * correctCount / totalCount) / 100
Expand Down Expand Up @@ -222,6 +232,8 @@ async function getTarget(argv: CliArgs) {
const ignoreAsAssertion = getArgOrCfgVal(['ignore-as-assertion', 'ignoreAsAssertion'])
const ignoreTypeAssertion = getArgOrCfgVal(['ignore-type-assertion', 'ignoreTypeAssertion'])
const ignoreNonNullAssertion = getArgOrCfgVal(['ignore-non-null-assertion', 'ignoreNonNullAssertion'])
const ignoreObject = getArgOrCfgVal(['ignore-object', 'ignoreObject'])
const ignoreEmptyType = getArgOrCfgVal(['ignore-empty-type', 'ignoreEmptyType'])
const showRelativePath = getArgOrCfgVal(['show-relative-path', 'showRelativePath'])
const historyFile = getArgOrCfgVal(['history-file', 'historyFile'])
const noDetailWhenFailed = getArgOrCfgVal(['no-detail-when-failed', 'noDetailWhenFailed'])
Expand All @@ -243,6 +255,8 @@ async function getTarget(argv: CliArgs) {
ignoreAsAssertion,
ignoreTypeAssertion,
ignoreNonNullAssertion,
ignoreObject,
ignoreEmptyType,
showRelativePath,
historyFile,
noDetailWhenFailed,
Expand Down
27 changes: 23 additions & 4 deletions packages/core/src/checker.ts
Expand Up @@ -46,8 +46,8 @@ function collectData(node: ts.Node, context: FileContext) {

if (types.length > 0) {
context.typeCheckResult.totalCount++
if (types.every((t) => typeIsAnyOrInTypeArguments(t, context.strict && !context.ignoreNested))) {
const kind = types.every((t) => typeIsAnyOrInTypeArguments(t, false)) ? FileAnyInfoKind.any : FileAnyInfoKind.containsAny
if (types.every((t) => typeIsAnyOrInTypeArguments(t, context.strict && !context.ignoreNested, context))) {
const kind = types.every((t) => typeIsAnyOrInTypeArguments(t, false, context)) ? FileAnyInfoKind.any : FileAnyInfoKind.containsAny
const success = collectAny(node, context, kind)
if (!success) {
collectNotAny(node, context, type)
Expand All @@ -58,14 +58,33 @@ function collectData(node: ts.Node, context: FileContext) {
}
}

function typeIsAnyOrInTypeArguments(type: ts.Type, anyCanBeInTypeArguments: boolean): boolean {
function typeIsAnyOrInTypeArguments(type: ts.Type, anyCanBeInTypeArguments: boolean, context: FileContext): boolean {
if (type.flags === ts.TypeFlags.Any) {
return (type as unknown as { intrinsicName: string }).intrinsicName === 'any'
}
if (type.flags === ts.TypeFlags.Object && type.symbol) {
// foo: Object
if (context.strict && !context.ignoreObject && type.symbol.escapedName === 'Object') {
return true
}
// foo: {}
if (
context.strict &&
!context.ignoreEmptyType &&
(type as unknown as { objectFlags: ts.ObjectFlags }).objectFlags === ts.ObjectFlags.Anonymous &&
type.getProperties().length === 0 &&
type.getCallSignatures().length === 0 &&
type.getConstructSignatures().length === 0 &&
type.symbol.flags === (ts.SymbolFlags.Transient | ts.SymbolFlags.TypeLiteral) &&
!type.symbol.members?.size
) {
return true
}
}
if (anyCanBeInTypeArguments && type.flags === ts.TypeFlags.Object) {
const typeArguments = (type as ts.TypeReference).typeArguments
if (typeArguments) {
return typeArguments.some((typeArgument) => typeIsAnyOrInTypeArguments(typeArgument, anyCanBeInTypeArguments))
return typeArguments.some((typeArgument) => typeIsAnyOrInTypeArguments(typeArgument, anyCanBeInTypeArguments, context))
}
}
return false
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/core.ts
Expand Up @@ -124,6 +124,8 @@ export async function lint(project: string, options?: Partial<LintOptions>) {
ignoreAsAssertion: lintOptions.ignoreAsAssertion,
ignoreTypeAssertion: lintOptions.ignoreTypeAssertion,
ignoreNonNullAssertion: lintOptions.ignoreNonNullAssertion,
ignoreObject: lintOptions.ignoreObject,
ignoreEmptyType: lintOptions.ignoreEmptyType,
}

sourceFile.forEachChild(node => {
Expand Down Expand Up @@ -178,6 +180,8 @@ const defaultLintOptions: LintOptions = {
ignoreAsAssertion: false,
ignoreTypeAssertion: false,
ignoreNonNullAssertion: false,
ignoreObject: false,
ignoreEmptyType: false,
}

/**
Expand Down Expand Up @@ -243,6 +247,8 @@ export function lintSync(compilerOptions: ts.CompilerOptions, rootNames: string[
ignoreAsAssertion: lintOptions.ignoreAsAssertion,
ignoreTypeAssertion: lintOptions.ignoreTypeAssertion,
ignoreNonNullAssertion: lintOptions.ignoreNonNullAssertion,
ignoreObject: lintOptions.ignoreObject,
ignoreEmptyType: lintOptions.ignoreEmptyType,
}

sourceFile.forEachChild(node => {
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/interfaces.ts
Expand Up @@ -67,6 +67,14 @@ interface CommonOptions {
* foo!
*/
ignoreNonNullAssertion: boolean
/**
* Object
*/
ignoreObject: boolean
/**
* {}
*/
ignoreEmptyType: boolean
}

export interface FileContext extends CommonOptions {
Expand Down

0 comments on commit a290acc

Please sign in to comment.