Skip to content

Commit

Permalink
feat: support --report-unused-ignore
Browse files Browse the repository at this point in the history
  • Loading branch information
plantain-00 committed Apr 3, 2024
1 parent baf6a95 commit 7de9fd9
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 19 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ name | type | description
`--cache-directory` | string? | [set cache directory](#enable-cache)(Added in `v2.24`)
`--not-only-in-cwd` | boolean? | include results outside current working directory(Added in `v2.26`)
`--json-output` | boolean? | output results as JSON(Added in `v2.27`)
`--report-unused-ignore` | boolean? | report unused [ignore line](#ignore-line) directives(Added in `v2.28`)

### strict mode

Expand Down Expand Up @@ -128,6 +129,7 @@ This tool will ignore the files, eg: `--ignore-files "demo1/*.ts" --ignore-files
"cacheDirectory": "custom-directory", // same as --cache-directory (Added in `v2.24`)
"notOnlyInCWD": true, // same as --not-only-in-cwd (Added in `v2.26`)
"jsonOutput": true, // same as --json-output (Added in `v2.27`)
"reportUnusedIgnore": true, // same as --report-unused-ignore (Added in `v2.28`)
},
```

Expand All @@ -142,6 +144,13 @@ try {
}
```

The `--report-unused-ignore` can report unused ignore line directives.(Added in `v2.28`)

```ts
// type-coverage:ignore-next-line
const a = 1 // <- this line is marked to ignore `any`, but there is no `any` here now.
```

## migrate to stricter typescript

Create a new tsconfig file(eg: `tconfig.type-coverage.json`) with stricter config, that extends from `tsc`'s `tsconfig.json`
Expand Down Expand Up @@ -212,6 +221,7 @@ export interface LintOptions {
reportSemanticError: boolean // Added in v2.22
cacheDirectory: string // Added in v2.24
notOnlyInCWD?: boolean, // Added in v2.26
reportUnusedIgnore: boolean, // Added in v2.28
}

export interface FileTypeCheckResult {
Expand Down
2 changes: 1 addition & 1 deletion clean-scripts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default {
],
dependencies: d.dependencies
}))),
...workspaces.map((d) => `node packages/cli/dist/index.js -p ${d.path}/src --detail --strict --suppressError`)
...workspaces.map((d) => `node packages/cli/dist/index.js -p ${d.path}/src --detail --strict --report-unused-ignore --suppressError`)
],
lint: {
ts: `eslint --ext .js,.ts ${tsFiles}`,
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function printHelp() {
--cache-directory string? set cache directory
--not-only-in-cwd boolean? include results outside current working directory
--json-output boolean? output results as JSON
--report-unused-ignore boolean? report unused ignore line directives
`)
}

Expand Down Expand Up @@ -111,6 +112,7 @@ interface CliArgs extends BaseArgs {
['update-if-higher']: boolean

['report-semantic-error']: boolean
['report-unused-ignore']: boolean
['cache-directory']: string
['not-only-in-cwd']: boolean
['json-output']: boolean
Expand All @@ -134,6 +136,7 @@ interface PkgArgs extends BaseArgs {
noDetailWhenFailed: boolean
updateIfHigher: boolean
reportSemanticError: boolean
reportUnusedIgnore: boolean
cacheDirectory: string
notOnlyInCWD: boolean
jsonOutput: boolean
Expand Down Expand Up @@ -182,6 +185,7 @@ async function executeCommandLine() {
historyFile,
noDetailWhenFailed,
reportSemanticError,
reportUnusedIgnore,
cacheDirectory,
notOnlyInCWD,
} = await getTarget(argv);
Expand All @@ -200,6 +204,7 @@ async function executeCommandLine() {
ignoreObject,
ignoreEmptyType,
reportSemanticError,
reportUnusedIgnore,
cacheDirectory,
notOnlyInCWD,
files: argv['--'].length > 0 ? argv['--'] : undefined,
Expand Down Expand Up @@ -302,6 +307,7 @@ async function getTarget(argv: CliArgs) {
const historyFile = getArgOrCfgVal(['history-file', 'historyFile'])
const noDetailWhenFailed = getArgOrCfgVal(['no-detail-when-failed', 'noDetailWhenFailed'])
const reportSemanticError = getArgOrCfgVal(['report-semantic-error', 'reportSemanticError'])
const reportUnusedIgnore = getArgOrCfgVal(['report-unused-ignore', 'reportUnusedIgnore'])
const cacheDirectory = getArgOrCfgVal(['cache-directory', 'cacheDirectory'])
const notOnlyInCWD = getArgOrCfgVal(['not-only-in-cwd', 'notOnlyInCWD'])

Expand All @@ -328,6 +334,7 @@ async function getTarget(argv: CliArgs) {
historyFile,
noDetailWhenFailed,
reportSemanticError,
reportUnusedIgnore,
cacheDirectory,
notOnlyInCWD,
};
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import * as ts from 'typescript'
import { FileAnyInfoKind, FileContext } from './interfaces'

function collectAny(node: ts.Node, context: FileContext, kind: FileAnyInfoKind) {
const { file, sourceFile, typeCheckResult, ingoreMap, ignoreUnreadAnys, debug, processAny } = context
const { file, sourceFile, typeCheckResult, ignoreLines, ignoreUnreadAnys, debug, processAny } = context
if (processAny !== undefined) {
return processAny(node, context)
}
const { line, character } = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile))
if (ingoreMap[file] && ingoreMap[file]?.has(line)) {
if (ignoreLines?.has(line)) {
if (!context.usedIgnoreLines) {
context.usedIgnoreLines = new Set()
}
context.usedIgnoreLines.add(line)
return false
}
if (ignoreUnreadAnys && isEvolvingAssignment(node)) {
Expand Down
25 changes: 20 additions & 5 deletions packages/core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from './interfaces'
import { checkNode } from './checker'
import { clearCacheOfDependencies, collectDependencies } from './dependencies'
import { collectIgnoreMap } from './ignore'
import { collectIgnoreLines } from './ignore'
import { readCache, getFileHash, saveCache } from './cache'

/**
Expand Down Expand Up @@ -106,7 +106,7 @@ export async function lint(project: string, options?: Partial<LintOptions>) {
continue
}

const ingoreMap = collectIgnoreMap(sourceFile, file)
const ignoreLines = collectIgnoreLines(sourceFile)
const context: FileContext = {
file,
sourceFile,
Expand All @@ -122,7 +122,7 @@ export async function lint(project: string, options?: Partial<LintOptions>) {
strict: lintOptions.strict,
processAny: lintOptions.processAny,
checker,
ingoreMap,
ignoreLines,
ignoreNested: lintOptions.ignoreNested,
ignoreAsAssertion: lintOptions.ignoreAsAssertion,
ignoreTypeAssertion: lintOptions.ignoreTypeAssertion,
Expand Down Expand Up @@ -158,6 +158,20 @@ export async function lint(project: string, options?: Partial<LintOptions>) {
checkNode(node, context)
})

if (lintOptions.reportUnusedIgnore && ignoreLines) {
for (const line of ignoreLines) {
if (!context.usedIgnoreLines?.has(line)) {
anys.push({
line,
character: 0,
text: 'Unused ignore line directive(no problems reported on that line)',
kind: FileAnyInfoKind.unusedIgnore,
file,
})
}
}
}

correctCount += context.typeCheckResult.correctCount
totalCount += context.typeCheckResult.totalCount
anys.push(...context.typeCheckResult.anys.map((a) => ({ file, ...a })))
Expand Down Expand Up @@ -209,6 +223,7 @@ const defaultLintOptions: LintOptions = {
ignoreObject: false,
ignoreEmptyType: false,
reportSemanticError: false,
reportUnusedIgnore: false,
}

/**
Expand Down Expand Up @@ -253,7 +268,7 @@ export function lintSync(compilerOptions: ts.CompilerOptions, rootNames: string[
const fileCounts =
new Map<string, Pick<FileTypeCheckResult, 'correctCount' | 'totalCount'>>()
for (const { sourceFile, file } of sourceFileInfos) {
const ingoreMap = collectIgnoreMap(sourceFile, file)
const ignoreLines = collectIgnoreLines(sourceFile)
const context: FileContext = {
file,
sourceFile,
Expand All @@ -269,7 +284,7 @@ export function lintSync(compilerOptions: ts.CompilerOptions, rootNames: string[
strict: lintOptions.strict,
processAny: lintOptions.processAny,
checker,
ingoreMap,
ignoreLines,
ignoreNested: lintOptions.ignoreNested,
ignoreAsAssertion: lintOptions.ignoreAsAssertion,
ignoreTypeAssertion: lintOptions.ignoreTypeAssertion,
Expand Down
19 changes: 9 additions & 10 deletions packages/core/src/ignore.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import * as ts from 'typescript'
import * as utils from 'tsutils/util'

export function collectIgnoreMap(sourceFile: ts.SourceFile, file: string) {
const ingoreMap: { [file: string]: Set<number> } = {}

export function collectIgnoreLines(sourceFile: ts.SourceFile) {
let ignoreLines: Set<number> | undefined
utils.forEachComment(sourceFile, (_, comment) => {
const commentText = comment.kind === ts.SyntaxKind.SingleLineCommentTrivia
? sourceFile.text.substring(comment.pos + 2, comment.end).trim()
: sourceFile.text.substring(comment.pos + 2, comment.end - 2).trim()
if (commentText.includes('type-coverage:ignore-next-line')) {
if (!ingoreMap[file]) {
ingoreMap[file] = new Set()
if (!ignoreLines) {
ignoreLines = new Set()
}
const line = ts.getLineAndCharacterOfPosition(sourceFile, comment.pos).line
ingoreMap[file]?.add(line + 1)
ignoreLines.add(line + 1)
} else if (commentText.includes('type-coverage:ignore-line')) {
if (!ingoreMap[file]) {
ingoreMap[file] = new Set()
if (!ignoreLines) {
ignoreLines = new Set()
}
const line = ts.getLineAndCharacterOfPosition(sourceFile, comment.pos).line
ingoreMap[file]?.add(line)
ignoreLines.add(line)
}
})

return ingoreMap
return ignoreLines
}
5 changes: 4 additions & 1 deletion packages/core/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const enum FileAnyInfoKind {
unsafeTypeAssertion = 4, // <string>foo
unsafeNonNull = 5, // foo!
semanticError = 6,
unusedIgnore = 7,
}

/**
Expand All @@ -45,6 +46,7 @@ export interface LintOptions extends CommonOptions {
fileCounts: boolean,
absolutePath?: boolean,
reportSemanticError: boolean
reportUnusedIgnore: boolean
cacheDirectory?: string
notOnlyInCWD?: boolean
}
Expand Down Expand Up @@ -87,7 +89,8 @@ export interface FileContext extends CommonOptions {
typeCheckResult: FileTypeCheckResult
checker: ts.TypeChecker
catchVariables: { [variable: string]: boolean }
ingoreMap: { [file: string]: Set<number> }
ignoreLines?: Set<number>
usedIgnoreLines?: Set<number>
}

interface TypeCheckCache extends FileTypeCheckResult {
Expand Down

0 comments on commit 7de9fd9

Please sign in to comment.