- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
因为团队内部开启了一个持续的前端代码质量改进计划,其中一个专项就是TS类型覆盖率,期间用到了 type-coverage 这个仓库,所以借这篇文章分享一下这个工具,并顺便从源码阅读的角度来分析一下该工具的源码,我自己fork了一个仓库,完成了中文版本的ReadMe文件并对核心代码添加了关键注释,需要的同学可以点击 传送门 .
type-coverage 是一个用于检查typescript代码的类型覆盖率的CLI工具,TS代码的类型覆盖率能够在某种程度上反映代码的质量水平(因为使用TS最主要的一个原因之一就是它所提供的类型安全保证).
type-coverage 该工具将检查所有标识符的类型, 类型覆盖率 = any类型的所有标识符 / 所有的标识符 ,值越高,则越优秀.
typescript-coverage-report 是用于生成 TypeScript 覆盖率报告的节点命令行工具,该工具基于 type-coverage 生成器实现.
工具的主要使用场景 。
(1) 呈现把项目的JS代码渐进迁移到TS代码的进度.
(2) 呈现把项目中宽松TS代码渐进转变为严格TS代码的进度.
(3) 避免引入意外的 any 。
(4) 持续改进该指标以提升代码的质量.
工具的安装 。
安装方式1 yarn global add type-coverage 。
安装方式2 pnpm i type-coverage -g 。
命令行工具 直接运行获取项目的TS类型覆盖率 。
$ pnpm type-coverage
669 / 689 97.09%
type-coverage success.
查看更多的操作指令 。
$ pnpm type-coverage --help
type-coverage [options] [-- file1.ts file2.ts ...]
-p, --project string? 告诉 CLI `tsconfig.json` 文件的位置
--detail boolean? 显示详情
--at-least number? 设置闸值,如果覆盖率小于该值,则失败
--debug boolean? 显示调试信息
--strict boolean? 是否开启严格模式
--ignore-catch boolean? 忽略捕获
--cache boolean? 是否使用缓存
--ignore-files string[]? 忽略文件
--ignore-unread boolean? 允许写入Any类型的变量
-h,--help boolean? 显示帮助信息
--is number? 设置闸值,如果覆盖率不等于该值,则失败
--update boolean? 如果 package.json 中存在typeCoverage选项,则根据 "atLeast" or "is" 值更新
--update-if-higher boolean? 如果新的类型覆盖率更高,则将package.json 中的"typeCoverage"更新到当前这个结果
--ignore-nested boolean? 忽略any类型的参数, eg: Promise<any>
--ignore-as-assertion boolean? 忽略 as 断言, eg: foo as string
--ignore-type-assertion boolean? 忽略类型断言, eg: <string>foo
--ignore-non-null-assertion boolean? 忽略非空断言, eg: foo!
--ignore-object boolean? Object 类型不视为 any,, eg: foo: Object
--ignore-empty-type boolean? 忽略空类型, eg: foo: {}
--show-relative-path boolean? 在详细信息中显示相对路径
--history-file string? 保存历史记录的文件名
--no-detail-when-failed boolean? 当CLI失败时不显示详细信息
--report-semantic-error boolean? 报告 typescript 语义错误
-- file1.ts file2.ts ... string[]? 仅检查这些文件, 适用于 lint-staged
--cache-directory string? 设置缓存目录
获取覆盖率的详情 。
$ pnpm type-coverage --detail
/wendingding/client/src/http.ts:21:52: headers
/wendingding/client/src/http.ts:24:14: headers
/wendingding/client/src/http.ts:28:19: headers
/wendingding/client/src/http.ts:35:20: data
/wendingding/client/src/http.ts:36:25: data
/wendingding/client/src/http.ts:38:8: error
/wendingding/client/src/http.ts:57:31: error
/wendingding/client/src/http.ts:66:44: data
/wendingding/client/src/http.ts:67:39: data
/wendingding/client/src/http.ts:74:43: data
/wendingding/client/src/http.ts:75:38: data
/wendingding/client/src/http.ts:78:45: data
/wendingding/client/src/http.ts:79:40: data
/wendingding/client/src/main.ts:22:37: el
/wendingding/client/src/main.ts:23:7: blocks
/wendingding/client/src/main.ts:23:16: el
/wendingding/client/src/main.ts:23:19: querySelectorAll
/wendingding/client/src/main.ts:24:3: blocks
/wendingding/client/src/main.ts:24:10: forEach
/wendingding/client/src/main.ts:24:19: block
669 / 689 97.09%
type-coverage success.
详情中的格式说明 文件路径:类型未覆盖变量所在的代码行:类型未覆盖变量在这一行中的位置(indexOf): 变量名 。
设置闸值 。
$ pnpm type-coverage --is 99
669 / 689 97.09%
The type coverage rate(97.09%) is not the target(99%).
严格模式 。
$ pnpm type-coverage --strict
667 / 689 96.80%
使用严格模式来进行计算,通常会降低类型覆盖率.
在严格模式下,具体执行的时候会有几点处理上的区别:
(1) 如果标识符的存在且至少包含一个 any , 比如 any[] , ReadonlyArray<any> , Promise<any> , Foo<number, any> ,那么他将被视为 any .
(2) 类型断言,类似 foo as string , foo! , <string>foo 将会被识别为未覆盖,排除 foo as const , <const>foo , foo as unknown 和由 isTypeAssignableTo 支持的其它安全类型断言 。
(3)Object类型(like foo: Object ) 和 空类型 (like foo: {} ) 将被视为any.
覆盖率报告 。
推荐使用来生成类型覆盖率的报告 安装包 。
$ yarn add --dev typescript-coverage-report
# OR
$ npm install --save-dev typescript-coverage-report
# OR
$ pnpm i typescript-coverage-report
运行包 。
$ yarn typescript-coverage-report
# OR
$ npm run typescript-coverage-report
# OR
pnpm typescript-coverage-report
其他更多的操作命令 。
% pnpm typescript-coverage-report --help
Usage: typescript-coverage-report [options]
Node command tool to generate TypeScript coverage report.
Options:
-V, --version 版本号
-o, --outputDir [string] 设置生产报告的位置(输出目录)
-t, --threshold [number] 需要的最低覆盖率. (默认值: 80)
-s, --strict [boolean] 是否开启严格模式. (默认值: 否)
-d, --debug [boolean] 是否显示调试信息. (默认值: 否)
-c, --cache [boolean] 保存并复用缓存中的类型检查结果. (默认值: 否)
-p, --project [string] tsconfig 文件的文件路径, eg: --project "./app/tsconfig.app.json" (默认值: ".")
-i, --ignore-files [string[]] 忽略指定文件, eg: --ignore-files "demo1/*.ts" --ignore-files "demo2/foo.ts" (默认值: 否)
--ignore-catch [boolean] ignore type any for (try-)catch clause variable (default: false)
-u, --ignore-unread [boolean] 允许写入具有Any类型的变量 (default: 否)
-h, --help 显示帮助信息
控制台的输出 。
上述命令执行后默认会在根目录生成 coverage-ts 文件夹,下面给出目录结构 。
.
├── assets
│ ├── source-file.css
│ └── source-file.js
├── files
│ └── src
├── index.html
└── typescript-coverage.json
打开 coverage-ts/index.html 文件,能够以网页的方式查看更加详细的报告内容.
通过网页版本的报告能够更直观的帮助我们来进行专项的优化.
VSCode插件 。
工具库还提供了 VSCVod插件版本 ,在VSCode插件管理器中,搜索然后安装插件后,在代码编写阶段就能够得到类型提示.
可以通过 Preferences - Settings - Extensions - Type Coverage 来对插件进行配置,如果 VSCode 插件的结果与 CLI 的结果不同,则有可能项目根目录的 tsconfig.json 与 CLI tsconfig.json 配置不同或存在冲突.
如果插件无法正常工作,可以参考下 issues/86#issuecomment 找找看有没有对应的解决办法 。
Pull PR中使用 。
当然,我们也可以把该工具集成到 。
项目的结构 。
.
├── cli
│ ├── README.md
│ ├── bin
│ │ └── type-coverage
│ ├── package.json
│ └── src
│ ├── index.ts
│ ├── lib.d.ts
│ └── tsconfig.json
├── core
│ ├── README.md
│ ├── package.json
│ └── src
│ ├── cache.ts
│ ├── checker.ts
│ ├── core.ts
│ ├── dependencies.ts
│ ├── ignore.ts
│ ├── index.ts
│ ├── interfaces.ts
│ ├── tsconfig.json
│ └── tsconfig.ts
├── plugin
│ ├── README.md
│ ├── package.json
│ └── src
│ ├── index.ts
│ └── tsconfig.json
└── vscode
├── README.md
├── package.json
└── src
├── index.ts
└── tsconfig.json
9 directories, 25 files
核心模块的关系 。
核心方法梳理 。
命令行工具的调用逻辑 。
async function executeCommandLine() {
/* 接收命令行参数 */
const argv = minimist(process.argv.slice(2), { '--': true }) as unknown as CliArgs
const { ... 省略 } = await getTarget(argv);
/* 核心主流程 */
const { correctCount, totalCount, anys } = await lint(project, {... 省略配置项});
/* 计算类型覆盖率 */
const percent = Math.floor(10000 * correctCount / totalCount) / 100
const percentString = percent.toFixed(2)
console.log(`${correctCount} / ${totalCount} ${percentString}%`)
}
主流程(Core) 。
//core.ts
const defaultLintOptions: LintOptions = {
debug: false,
files: undefined,
oldProgram: undefined,
strict: false,
enableCache: false,
ignoreCatch: false,
ignoreFiles: undefined,
ignoreUnreadAnys: false,
fileCounts: false,
ignoreNested: false,
ignoreAsAssertion: false,
ignoreTypeAssertion: false,
ignoreNonNullAssertion: false,
ignoreObject: false,
ignoreEmptyType: false,
reportSemanticError: false,
}
/* 核心函数:主流程 */
export async function lint(project: string, options?: Partial<LintOptions>) {
/* 检测的前置条件(参数) */
const lintOptions = { ...defaultLintOptions, ...options }
/* 获取项目的根目录和编译选项 */
const { rootNames, compilerOptions } = await getProjectRootNamesAndCompilerOptions(project)
/* 通过ts创建处理程序 */
const program = ts.createProgram(rootNames, compilerOptions, undefined, lintOptions.oldProgram)
/* 获取类型检查器 */
const checker = program.getTypeChecker()
/* 声明用于保存文件的集合 */
const allFiles = new Set<string>()
/* 声明用于保存文件信息的数组 */
const sourceFileInfos: SourceFileInfo[] = []
/* 根据配置参数从缓存中读取类型检查结果(缓存的数据) */
const typeCheckResult = await readCache(lintOptions.enableCache, lintOptions.cacheDirectory)
/* 读取配置参数中的忽略文件信息 */
const ignoreFileGlobs = lintOptions.ignoreFiles
? (typeof lintOptions.ignoreFiles === 'string'
? [lintOptions.ignoreFiles]
: lintOptions.ignoreFiles)
: undefined
/* 获取所有的SourceFiles并遍历 */
for (const sourceFile of program.getSourceFiles()) {
let file = sourceFile.fileName
if (!file.includes('node_modules')) {
/* 如果不是绝对路径 */
if (!lintOptions.absolutePath) {
/* process.cwd() 是当前Node进程执行时的文件夹地址,也就是工作目录,保证了文件在不同的目录下执行时,路径始终不变 */
/* __dirname 是被执行的js文件的地址,也就是文件所在目录 */
/* 计算得到文件的相对路径 */
file = path.relative(process.cwd(), file)
/* 如果路径以..开头则跳过该文件 */
if (file.startsWith('..')) {
continue
}
}
/* 如果lintOptions.files中不包含该文件,则跳过 */
if (lintOptions.files && !lintOptions.files.includes(file)) {
continue
}
/* 如果该文件存在于忽略配置项中,则跳过 */
if (ignoreFileGlobs && ignoreFileGlobs.some((f) => minimatch(file, f))) {
continue
}
/* 添加文件到集合 */
allFiles.add(file)
/* 计算文件的哈希值 */
const hash = await getFileHash(file, lintOptions.enableCache)
/* 检查该文件是否存在缓存数据 */
const cache = typeCheckResult.cache[file]
/* 如果存在缓存数据 */
if (cache) {
/* 如果配置项定义了ignoreNested 则忽略 嵌套的any */
if (lintOptions.ignoreNested) {
cache.anys = cache.anys.filter((c) => c.kind !== FileAnyInfoKind.containsAny)
}
/* 如果配置项定义了ignoreAsAssertion 则忽略 不安全的as */
if (lintOptions.ignoreAsAssertion) {
cache.anys = cache.anys.filter((c) => c.kind !== FileAnyInfoKind.unsafeAs)
}
/* 如果配置项定义了ignoreTypeAssertion 则忽略 不安全的类型断言 */
if (lintOptions.ignoreTypeAssertion) {
cache.anys = cache.anys.filter((c) => c.kind !== FileAnyInfoKind.unsafeTypeAssertion)
}
/* 如果配置项定义了ignoreNonNullAssertion 则忽略 不安全的非空断言 */
if (lintOptions.ignoreNonNullAssertion) {
cache.anys = cache.anys.filter((c) => c.kind !== FileAnyInfoKind.unsafeNonNull)
}
}
/* 更新sourceFileInfos对象数组 */
sourceFileInfos.push({
file, /* 文件路径 */
sourceFile,
hash,/* 哈希值 */
cache: cache && cache.hash === hash ? cache : undefined /* 该文件的缓存信息 */
})
}
}
/* 如果启用了缓存 */
if (lintOptions.enableCache) {
/* 获取依赖集合 */
const dependencies = collectDependencies(sourceFileInfos, allFiles)
/* 遍历sourceFileInfos */
for (const sourceFileInfo of sourceFileInfos) {
/* 如果没有使用缓存,那就清理依赖 */
if (!sourceFileInfo.cache) {
clearCacheOfDependencies(sourceFileInfo, dependencies, sourceFileInfos)
}
}
}
let correctCount = 0
let totalCount = 0
/* 声明anys数组 */
const anys: AnyInfo[] = []
/* 声明fileCounts映射 */
const fileCounts =
new Map<string, Pick<FileTypeCheckResult, 'correctCount' | 'totalCount'>>()
/* 遍历sourceFileInfos */
for (const { sourceFile, file, hash, cache } of sourceFileInfos) {
/* 如果存在缓存,那么直接根据缓存处理后就跳过 */
if (cache) {
/* 累加correctCount和totalCount */
correctCount += cache.correctCount
totalCount += cache.totalCount
/* 把缓存的anys合并到anys数据中 */
anys.push(...cache.anys.map((a) => ({ file, ...a })))
if (lintOptions.fileCounts) {
/* 统计每个文件的数据 */
fileCounts.set(file, {
correctCount: cache.correctCount,
totalCount: cache.totalCount,
})
}
continue
}
/* 获取忽略的集合 */
const ingoreMap = collectIgnoreMap(sourceFile, file)
/* 组织上下文对象 */
const context: FileContext = {
file,
sourceFile,
typeCheckResult: {
correctCount: 0,
totalCount: 0,
anys: []
},
ignoreCatch: lintOptions.ignoreCatch,
ignoreUnreadAnys: lintOptions.ignoreUnreadAnys,
catchVariables: {},
debug: lintOptions.debug,
strict: lintOptions.strict,
processAny: lintOptions.processAny,
checker,
ingoreMap,
ignoreNested: lintOptions.ignoreNested,
ignoreAsAssertion: lintOptions.ignoreAsAssertion,
ignoreTypeAssertion: lintOptions.ignoreTypeAssertion,
ignoreNonNullAssertion: lintOptions.ignoreNonNullAssertion,
ignoreObject: lintOptions.ignoreObject,
ignoreEmptyType: lintOptions.ignoreEmptyType,
}
/* 关键流程:单个文件遍历所有的子节点 */
sourceFile.forEachChild(node => {
/* 检测节点,并更新context的值 */
/* ?为什么选择引用传递?? */
checkNode(node, context)
})
/* 更新correctCount 把当前文件的数据累加上*/
correctCount += context.typeCheckResult.correctCount
/* 更新totalCount 把当前文件的数据累加上*/
totalCount += context.typeCheckResult.totalCount
/* 把当前文件的anys数据累加 */
anys.push(...context.typeCheckResult.anys.map((a) => ({ file, ...a })))
if (lintOptions.reportSemanticError) {
const diagnostics = program.getSemanticDiagnostics(sourceFile)
for (const diagnostic of diagnostics) {
if (diagnostic.start !== undefined) {
totalCount++
let text: string
if (typeof diagnostic.messageText === 'string') {
text = diagnostic.messageText
} else {
text = diagnostic.messageText.messageText
}
const { line, character } = ts.getLineAndCharacterOfPosition(sourceFile, diagnostic.start)
anys.push({
line,
character,
text,
kind: FileAnyInfoKind.semanticError,
file,
})
}
}
}
/* 如果需要统计每个文件的信息 */
if (lintOptions.fileCounts) {
/* 更新当前文件的统计结果 */
fileCounts.set(file, {
correctCount: context.typeCheckResult.correctCount,
totalCount: context.typeCheckResult.totalCount
})
}
/* 如果启用了缓存 */
if (lintOptions.enableCache) {
/* 把本次计算的结果保存一份到缓存对象中 */
const resultCache = typeCheckResult.cache[file]
/* 如果该缓存对象已经存在,那么就更新数据,否则那就新建缓存对象 */
if (resultCache) {
resultCache.hash = hash
resultCache.correctCount = context.typeCheckResult.correctCount
resultCache.totalCount = context.typeCheckResult.totalCount
resultCache.anys = context.typeCheckResult.anys
} else {
typeCheckResult.cache[file] = {
hash,
...context.typeCheckResult
}
}
}
}
/* 再操作的最后,检查是否启用了缓存 */
if (lintOptions.enableCache) {
/* 如果启用了缓存,那就把缓存数据保存起来 */
await saveCache(typeCheckResult, lintOptions.cacheDirectory)
}
// 返回计算的结果
return { correctCount, totalCount, anys, program, fileCounts }
}
在 core.ts 文件中,提供了两个同步和异步两种方式来执行任务,他们分别是 lint 和 lintSync ,上面的代码给出了核心代码和部分注释,整体来看处理逻辑比较简单,这里给出源码中这些函数间的关系图.
核心逻辑就是根据目录先获取所有的文件,然后再遍历这些文件,接着依次处理每个文件中的标识符,统计correctCount、totalCount和anys.
关键模块(checker) 。
//checkNode方法 核心检测函数(递归调用)
export function checkNode(node: ts.Node | undefined, context: FileContext): void {
if (node === undefined) {
return
}
if (context.debug) {
const { line, character } = ts.getLineAndCharacterOfPosition(context.sourceFile, node.getStart(context.sourceFile))
console.log(`node: ${context.file}:${line + 1}:${character + 1}: ${node.getText(context.sourceFile)} ${node.kind}(kind)`)
}
checkNodes(node.decorators, context)
checkNodes(node.modifiers, context)
if (skippedNodeKinds.has(node.kind)) {
return
}
/* 关键字 */
if (node.kind === ts.SyntaxKind.ThisKeyword) {
collectData(node, context)
return
}
/* 标识符 */
if (ts.isIdentifier(node)) {
if (context.catchVariables[node.escapedText as string]) {
return
}
collectData(node, context)
return
}
/* 其他处理 */
if (ts.isQualifiedName(node)) {
checkNode(node.left, context)
checkNode(node.right, context)
return
}
}
// Nodes 检查
function checkNodes(nodes: ts.NodeArray<ts.Node> | undefined, context: FileContext): void {
if (nodes === undefined) {
return
}
for (const node of nodes) {
checkNode(node, context)
}
}
/* 收集器 */
function collectData(node: ts.Node, context: FileContext) {
const types: ts.Type[] = []
const type = context.checker.getTypeAtLocation(node)
if (type) {
types.push(type)
}
const contextualType = context.checker.getContextualType(node as ts.Expression)
if (contextualType) {
types.push(contextualType)
}
if (types.length > 0) {
context.typeCheckResult.totalCount++
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) {
//收集所有的any
collectNotAny(node, context, type)
}
} else {
//收集所有的 notAny
collectNotAny(node, context, type)
}
}
}
主流程和checker类型检查模块的函数调用关系 。
项目源码中核心函数间的调用关系图 。
按照功能模块来划分的话,主要包含主函数lint、缓存处理(saveCache、readCache等)、和类型检查(checkNode等),其中checkNode中涉及到了很多Node节点的类型,而 ts. 相关的方法也都值得关注.
模块(包) | 描述 |
---|---|
Definitely Typed | TypeScript 类型定义 |
minimist | 命令行参数解析器 |
fast-glob | 文件系统操作 |
minimatch | 路径匹配库 |
normalize-path | 规范化路径 |
clean-scripts | 元文件脚本清理CLI |
clean-release | 文件操作的CLI |
rimraf | 删除文件(夹) |
tslib | TS运行时库 |
tsutils | Typescript工具函数 |
Definitely Typed 这是一个TypeScript 类型定义的仓库,下面这些依赖都根植于这个库 。
@types/minimatch
@types/minimist
@types/node
@types/normalize-path
minimist 是参数解析器核心库.
在项目中的使用场景 。
//文件路径 packages/cli/src/index.ts
import minimist = require('minimist')
//...
const argv = minimist(process.argv.slice(2), { '--': true }) as unknown as CliArgs
const showVersion = argv.v || argv.version
...
fast-glob 遍历文件系统并根据 Unix Bash shell 使用的规则返回匹配指定模式的路径名集合.
项目中的使用场景 。
//文件路径 /packages/core/src/tsconfig.ts
import fg = require('fast-glob')
//...
const includeFiles = await fg(rules, {
ignore,
cwd: dirname,
})
files.push(...includeFiles)
minimatch 是 npm 内部使用的匹配库,它通过将 glob 表达式转换为 JavaScriptRegExp 对象来工作.
项目中的使用场景 。
//文件路径 /packages/core/src/core.ts#L3
import minimatch = require('minimatch')
//...
if (ignoreFileGlobs && ignoreFileGlobs.some((f) => minimatch(file, f))) {
continue
}
具体的使用示例 。
minimatch("bar.foo", "*.foo") // true!
minimatch("bar.foo", "*.bar") // false!
minimatch("bar.foo", "*.+(bar|foo)", { debug: true }) // true, and noisy!
minimatch('/a/b', '/a/*/c/d', { partial: true }) // true, might be /a/b/c/d
minimatch('/a/b', '/**/d', { partial: true }) // true, might be /a/b/.../d
minimatch('/x/y/z', '/a/**/z', { partial: true }) // false, because x !== a
normalize-path 规范化路径的库.
项目中的使用场景 。
import normalize = require('normalize-path')
async function getRootNames(config: JsonConfig, dirname: string) {
//...
let rules: string[] = []
for (const file of include) {
const currentPath = path.resolve(dirname, file)
const stats = await statAsync(currentPath)
if (stats === undefined || stats.isFile()) {
rules.push(currentPath)
} else if (stats.isDirectory()) {
rules.push(`${currentPath.endsWith('/') ? currentPath.substring(0, currentPath.length - 1) : currentPath}/**/*`)
}
}
rules = rules.map((r) => normalize(r))
ignore = ignore.map((r) => normalize(r))
}
clean-scripts 是使 package.json脚本干净的 CLI 工具.
项目中的使用场景 。
//文件路径 /clean-scripts.config.ts
import { Tasks, readWorkspaceDependencies } from 'clean-scripts'
const tsFiles = `"packages/**/src/**/*.ts"`
const workspaces = readWorkspaceDependencies()
export default {
build: [
new Tasks(workspaces.map((d) => ({
name: d.name,
script: [
`rimraf ${d.path}/dist/`,
`tsc -p ${d.path}/src/`,
],
dependencies: d.dependencies
}))),
...workspaces.map((d) => `node packages/cli/dist/index.js -p ${d.path}/src --detail --strict --suppressError`)
],
lint: {
ts: `eslint --ext .js,.ts ${tsFiles}`,
export: `no-unused-export ${tsFiles} --need-module tslib --need-module ts-plugin-type-coverage --ignore-module vscode --strict`,
markdown: `markdownlint README.md`
},
test: [],
fix: `eslint --ext .js,.ts ${tsFiles} --fix`
}
在上面的build命令中发现了 rimraf ,在lint命令中发现了 tslib 这个工具包.
tslib 是一个用于TypeScript的运行时库,其中包含所有 TypeScript 辅助函数.
rimraf 以包的形式包装rm -rf命令,用来删除文件和文件夹的,不管文件夹是否为空,都可删除.
适用场景 : 项目中build文件的时候每次都会生成一个dist目录,有时需要把dist目录里的所以旧文件全部删掉,就可以使用rimraf命令 。
安装和使用 。
$ pnpm install rimraf -g
clean-release 是 CLI 工具,用于将要发布的文件复制到tmp clean 目录中,用于 npm 发布、electronjs打包、docker映像创建或部署.
项目中的使用 。
//文件路径 /clean-release.config.ts#L8
import { Configuration } from 'clean-release'
const config: Configuration = {
include: [
'packages/*/dist/*',
'packages/*/es/*',
'packages/*/bin/*',
'packages/*/package.json',
'packages/*/README.md',
],
exclude: [
],
askVersion: true,
changesGitStaged: true,
postScript: ({ dir, tag, version, effectedWorkspacePaths = [] }) => [
...effectedWorkspacePaths.map((w) => w.map((e) => {
if (e === 'packages/vscode') {
return tag ? undefined : `cd "${dir}/${e}" && yarn install --registry=https://registry.npmjs.org/ && rm -f "${dir}/yarn.lock" && vsce publish ${version}`
}
return tag
? `npm publish "${dir}/${e}" --access public --tag ${tag}`
: `npm publish "${dir}/${e}" --access public`
})),
`git-commits-to-changelog --release ${version}`,
'git add CHANGELOG.md',
`git commit -m "${version}"`,
`git tag -a v${version} -m 'v${version}'`,
'git push',
`git push origin v${version}`
]
}
export default config
tsutils Typescript工具函数.
在type-coverage的项目中,该工具库用来处理忽略,即当注释中存在 type-coverage:ignore-line 和 type-coverage:ignore-next-line 的时候,这部分代码将不被记入到类型覆盖率的计算中.
下面列出项目中使用该工具的代码部分 。
//文件路径:/packages/core/src/ignore.ts#L4
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> } = {}
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()
}
const line = ts.getLineAndCharacterOfPosition(sourceFile, comment.pos).line
ingoreMap[file]?.add(line + 1)
} else if (commentText.includes('type-coverage:ignore-line')) {
if (!ingoreMap[file]) {
ingoreMap[file] = new Set()
}
const line = ts.getLineAndCharacterOfPosition(sourceFile, comment.pos).line
ingoreMap[file]?.add(line)
}
})
return ingoreMap
}
最后此篇关于源码解读之TypeScript类型覆盖检测工具type-coverage的文章就讲到这里了,如果你想了解更多关于源码解读之TypeScript类型覆盖检测工具type-coverage的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试编写一个相当多态的库。我遇到了一种更容易表现出来却很难说出来的情况。它看起来有点像这样: {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE
谁能解释一下这个表达式是如何工作的? type = type || 'any'; 这是否意味着如果类型未定义则使用“任意”? 最佳答案 如果 type 为“falsy”(即 false,或 undef
我有一个界面,在IAnimal.fs中, namespace Kingdom type IAnimal = abstract member Eat : Food -> unit 以及另一个成功
这个问题在这里已经有了答案: 关闭 10 年前。 Possible Duplicate: What is the difference between (type)value and type(va
在 C# 中,default(Nullable) 之间有区别吗? (或 default(long?) )和 default(long) ? Long只是一个例子,它可以是任何其他struct类型。 最
假设我有一个案例类: case class Foo(num: Int, str: String, bool: Boolean) 现在我还有一个简单的包装器: sealed trait Wrapper[
这个问题在这里已经有了答案: Create C# delegate type with ref parameter at runtime (1 个回答) 关闭 2 年前。 为了即时创建委托(dele
我正在尝试获取图像的 dct。一开始我遇到了错误 The function/feature is not implemented (Odd-size DCT's are not implemented
我正在尝试使用 AFNetworking 的 AFPropertyListRequestOperation,但是当我尝试下载它时,出现错误 预期的内容类型{( “应用程序/x-plist” )}, 得
我在下面收到错误。我知道这段代码的意思,但我不知道界面应该是什么样子: Element implicitly has an 'any' type because index expression is
我尝试将 SignalType 从 ReactiveCocoa 扩展为自定义 ErrorType,代码如下所示 enum MyError: ErrorType { // .. cases }
我无法在任何其他问题中找到答案。假设我有一个抽象父类(super class) Abstract0,它有两个子类 Concrete1 和 Concrete1。我希望能够在 Abstract0 中定义类
我想知道为什么这个索引没有用在 RANGE 类型中,而是用在 INDEX 中: 索引: CREATE INDEX myindex ON orders(order_date); 查询: EXPLAIN
我正在使用 RxJava,现在我尝试通过提供 lambda 来订阅可观察对象: observableProvider.stringForKey(CURRENT_DELETED_ID) .sub
我已经尝试了几乎所有解决问题的方法,其中包括。为 提供类型使用app.use(express.static('public'))还有更多,但我似乎无法为此找到解决方案。 index.js : imp
以下哪个 CSS 选择器更快? input[type="submit"] { /* styles */ } 或 [type="submit"] { /* styles */ } 只是好
我不知道这个设置有什么问题,我在 IDEA 中获得了所有注释(@Controller、@Repository、@Service),它在行号左侧显示 bean,然后转到该 bean。 这是错误: 14-
我听从了建议 registering java function as a callback in C function并且可以使用“简单”类型(例如整数和字符串)进行回调,例如: jstring j
有一些 java 类,加载到 Oracle 数据库(版本 11g)和 pl/sql 函数包装器: create or replace function getDataFromJava( in_uLis
我已经从 David Walsh 的 css 动画回调中获取代码并将其修改为 TypeScript。但是,我收到一个错误,我不知道为什么: interface IBrowserPrefix { [
我是一名优秀的程序员,十分优秀!