Skip to content

Commit

Permalink
feat: split strict flag
Browse files Browse the repository at this point in the history
  • Loading branch information
plantain-00 committed Feb 22, 2021
1 parent 1a24073 commit 2a4f62b
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 39 deletions.
14 changes: 13 additions & 1 deletion README.md
Expand Up @@ -43,6 +43,10 @@ name | type | description
`--is` | number? | fail if coverage rate !== this value(Added in `v2.6`)
`--update` | boolean? | update "typeCoverage" in package.json to current result(Added in `v2.6`)
`--ignore-unread` | boolean? | allow writes to variables with implicit any types(Added in `v2.14`)
`--ignore-nested` | boolean? | ignore any in type arguments, eg: `Promise<any>`(Added in `v2.16`)
`--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`)

### strict mode

Expand Down Expand Up @@ -95,7 +99,11 @@ This tool will ignore the files, eg: `--ignore-files "demo1/*.ts" --ignore-files
"strict": true, // same as --strict (Added in `v2.11`)
"suppressError": true, // same as --suppressError (Added in `v2.11`)
"update": true, // same as --update (Added in `v2.11`)
"ignoreUnread": true // same as --ignore-unread (Added in `v2.14`)
"ignoreUnread": true, // same as --ignore-unread (Added in `v2.14`)
"ignoreNested": true, // same as --ignore-nested (Added in `v2.16`)
"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`)
},
```

Expand Down Expand Up @@ -154,6 +162,10 @@ export interface LintOptions {
absolutePath?: boolean, // Added in v2.4
processAny?: ProccessAny, // Added in v2.7
ignoreUnreadAnys: boolean, // Added in v2.14
ignoreNested: boolean // Added in v2.16
ignoreAsAssertion: boolean // Added in v2.16
ignoreTypeAssertion: boolean // Added in v2.16
ignoreNonNullAssertion: boolean // Added in v2.16
}

export interface FileTypeCheckResult {
Expand Down
94 changes: 74 additions & 20 deletions packages/cli/src/index.ts
Expand Up @@ -18,18 +18,22 @@ function showToolVersion() {
function printHelp() {
console.log(`type-coverage [options]
-p, --project string? tell the CLI where is the tsconfig.json
--detail boolean? show detail
--at-least number? fail if coverage rate < this value
--debug boolean? show debug info
--strict boolean? strict mode
--ignore-catch boolean? ignore catch
--cache boolean? enable cache
--ignore-files string[]? ignore files
--ignore-unread boolean? allow writes to variables with implicit any types
-h,--help boolean? show help
--is number? fail if coverage rate !== this value
--update boolean? update "typeCoverage" in package.json to current result
-p, --project string? tell the CLI where is the tsconfig.json
--detail boolean? show detail
--at-least number? fail if coverage rate < this value
--debug boolean? show debug info
--strict boolean? strict mode
--ignore-catch boolean? ignore catch
--cache boolean? enable cache
--ignore-files string[]? ignore files
--ignore-unread boolean? allow writes to variables with implicit any types
-h,--help boolean? show help
--is number? fail if coverage rate !== this value
--update boolean? update "typeCoverage" in package.json to current result
--ignore-nested boolean? ignore any in type arguments, eg: Promise<any>
--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!
`)
}

Expand All @@ -54,13 +58,23 @@ interface CliArgs extends BaseArgs {
['ignore-files']?: string | string[]
['at-least']: number
['ignore-unread']: boolean

['ignore-nested']: boolean
['ignore-as-assertion']: boolean
['ignore-type-assertion']: boolean
['ignore-non-null-assertion']: boolean
}

interface PkgArgs extends BaseArgs {
ignoreCatch: boolean
ignoreFiles?: string | string[]
ignoreUnread: boolean
atLeast: boolean

ignoreNested: boolean
ignoreAsAssertion: boolean
ignoreTypeAssertion: boolean
ignoreNonNullAssertion: boolean
}

interface PackageJson {
Expand All @@ -83,15 +97,35 @@ async function executeCommandLine() {
process.exit(0)
}

const { atLeast, debug, detail, enableCache, ignoreCatch, ignoreFiles, ignoreUnread, is, project, strict, update } = await getTarget(argv);
const {
atLeast,
debug,
detail,
enableCache,
ignoreCatch,
ignoreFiles,
ignoreUnread,
is,
project,
strict,
update,
ignoreNested,
ignoreAsAssertion,
ignoreTypeAssertion,
ignoreNonNullAssertion
} = await getTarget(argv);

const { correctCount, totalCount, anys } = await lint(project, {
debug: debug,
strict: strict,
enableCache: enableCache,
ignoreCatch: ignoreCatch,
ignoreFiles: ignoreFiles,
debug,
strict,
enableCache,
ignoreCatch,
ignoreFiles,
ignoreUnreadAnys: ignoreUnread,
ignoreNested,
ignoreAsAssertion: ignoreAsAssertion,
ignoreTypeAssertion,
ignoreNonNullAssertion,
});

const percent = Math.floor(10000 * correctCount / totalCount) / 100
Expand Down Expand Up @@ -159,8 +193,28 @@ async function getTarget(argv: CliArgs) {
const project = getArgOrCfgVal(['p', 'project']) || '.'
const strict = getArgOrCfgVal(['strict'])
const update = getArgOrCfgVal(['update'])

return { atLeast, debug, detail, enableCache, ignoreCatch, ignoreFiles, ignoreUnread, is, project, strict, update };
const ignoreNested = getArgOrCfgVal(['ignore-nested', 'ignoreNested'])
const ignoreAsAssertion = getArgOrCfgVal(['ignore-as-assertion', 'ignoreAsAssertion'])
const ignoreTypeAssertion = getArgOrCfgVal(['ignore-type-assertion', 'ignoreTypeAssertion'])
const ignoreNonNullAssertion = getArgOrCfgVal(['ignore-non-null-assertion', 'ignoreNonNullAssertion'])

return {
atLeast,
debug,
detail,
enableCache,
ignoreCatch,
ignoreFiles,
ignoreUnread,
is,
project,
strict,
update,
ignoreNested,
ignoreAsAssertion,
ignoreTypeAssertion,
ignoreNonNullAssertion,
};
}

async function saveTarget(target: number) {
Expand Down
26 changes: 19 additions & 7 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) => typeIsStrictAny(t, context.strict))) {
const kind = types.every((t) => typeIsStrictAny(t, false)) ? FileAnyInfoKind.any : FileAnyInfoKind.containsAny
if (types.every((t) => typeIsAnyOrInTypeArguments(t, context.strict && !context.ignoreNested))) {
const kind = types.every((t) => typeIsAnyOrInTypeArguments(t, false)) ? FileAnyInfoKind.any : FileAnyInfoKind.containsAny
const success = collectAny(node, context, kind)
if (!success) {
collectNotAny(node, context, type)
Expand All @@ -58,14 +58,14 @@ function collectData(node: ts.Node, context: FileContext) {
}
}

function typeIsStrictAny(type: ts.Type, strict: boolean): boolean {
function typeIsAnyOrInTypeArguments(type: ts.Type, anyCanBeInTypeArguments: boolean): boolean {
if (type.flags === ts.TypeFlags.Any) {
return (type as unknown as { intrinsicName: string }).intrinsicName === 'any'
}
if (strict && type.flags === ts.TypeFlags.Object) {
if (anyCanBeInTypeArguments && type.flags === ts.TypeFlags.Object) {
const typeArguments = (type as ts.TypeReference).typeArguments
if (typeArguments) {
return typeArguments.some((typeArgument) => typeIsStrictAny(typeArgument, strict))
return typeArguments.some((typeArgument) => typeIsAnyOrInTypeArguments(typeArgument, anyCanBeInTypeArguments))
}
}
return false
Expand Down Expand Up @@ -95,10 +95,22 @@ function checkNodes(nodes: ts.NodeArray<ts.Node> | undefined, context: FileConte
}
}

const isTypeAssertionExpression = ts.isTypeAssertionExpression || ts.isTypeAssertion

function checkTypeAssertion(node: ts.Node, context: FileContext, kind: FileAnyInfoKind) {
if (context.strict) {
if (kind === FileAnyInfoKind.unsafeNonNull && context.ignoreNonNullAssertion) {
return
}
if (kind === FileAnyInfoKind.unsafeAs && context.ignoreAsAssertion) {
return
}
if (kind === FileAnyInfoKind.unsafeTypeAssertion && context.ignoreTypeAssertion) {
return
}

// include `foo as any` and `<any>foo`
if ((ts.isAsExpression(node) || ts.isTypeAssertion(node)) && node.type.kind !== ts.SyntaxKind.AnyKeyword) {
if ((ts.isAsExpression(node) || isTypeAssertionExpression(node)) && node.type.kind !== ts.SyntaxKind.AnyKeyword) {
// exclude `foo as const` and `<const>foo`
if (ts.isTypeReferenceNode(node.type) && node.type.getText() === 'const') {
return
Expand Down Expand Up @@ -334,7 +346,7 @@ export function checkNode(node: ts.Node | undefined, context: FileContext): void
checkNodes(node.typeArguments, context)
return
}
if (ts.isTypeAssertion(node)) {
if (isTypeAssertionExpression(node)) {
checkTypeAssertion(node, context, FileAnyInfoKind.unsafeTypeAssertion)
checkNode(node.expression, context)
checkNode(node.type, context)
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/core.ts
Expand Up @@ -105,6 +105,10 @@ export async function lint(project: string, options?: Partial<LintOptions>) {
processAny: lintOptions.processAny,
checker,
ingoreMap,
ignoreNested: lintOptions.ignoreNested,
ignoreAsAssertion: lintOptions.ignoreAsAssertion,
ignoreTypeAssertion: lintOptions.ignoreTypeAssertion,
ignoreNonNullAssertion: lintOptions.ignoreNonNullAssertion,
}

sourceFile.forEachChild(node => {
Expand Down Expand Up @@ -155,6 +159,10 @@ const defaultLintOptions: LintOptions = {
ignoreFiles: undefined,
ignoreUnreadAnys: false,
fileCounts: false,
ignoreNested: false,
ignoreAsAssertion: false,
ignoreTypeAssertion: false,
ignoreNonNullAssertion: false,
}

/**
Expand Down Expand Up @@ -216,6 +224,10 @@ export function lintSync(compilerOptions: ts.CompilerOptions, rootNames: string[
processAny: lintOptions.processAny,
checker,
ingoreMap,
ignoreNested: lintOptions.ignoreNested,
ignoreAsAssertion: lintOptions.ignoreAsAssertion,
ignoreTypeAssertion: lintOptions.ignoreTypeAssertion,
ignoreNonNullAssertion: lintOptions.ignoreNonNullAssertion,
}

sourceFile.forEachChild(node => {
Expand Down
36 changes: 25 additions & 11 deletions packages/core/src/interfaces.ts
Expand Up @@ -36,32 +36,46 @@ export const enum FileAnyInfoKind {
*/
export type ProccessAny = (node: ts.Node, context: FileContext) => boolean

export interface LintOptions {
debug: boolean,
export interface LintOptions extends CommonOptions {
files?: string[],
oldProgram?: ts.Program,
strict: boolean,
enableCache: boolean,
ignoreCatch: boolean,
ignoreFiles?: string | string[],
ignoreUnreadAnys: boolean,
fileCounts: boolean,
absolutePath?: boolean,
}

interface CommonOptions {
debug: boolean,
strict: boolean,
ignoreCatch: boolean,
ignoreUnreadAnys: boolean,
processAny?: ProccessAny,
/**
* Promise<any>
*/
ignoreNested: boolean
/**
* foo as string
*/
ignoreAsAssertion: boolean
/**
* <string>foo
*/
ignoreTypeAssertion: boolean
/**
* foo!
*/
ignoreNonNullAssertion: boolean
}

export interface FileContext {
export interface FileContext extends CommonOptions {
file: string
sourceFile: ts.SourceFile
typeCheckResult: FileTypeCheckResult
debug: boolean
strict: boolean
checker: ts.TypeChecker
ignoreCatch: boolean
ignoreUnreadAnys: boolean
catchVariables: { [variable: string]: boolean }
ingoreMap: { [file: string]: Set<number> }
processAny?: ProccessAny
}

interface TypeCheckCache extends FileTypeCheckResult {
Expand Down

0 comments on commit 2a4f62b

Please sign in to comment.