diff --git a/packages/canyon-backend/package.json b/packages/canyon-backend/package.json index a8fe3cfb..ec1f2cd1 100755 --- a/packages/canyon-backend/package.json +++ b/packages/canyon-backend/package.json @@ -25,7 +25,7 @@ "@nestjs/typeorm": "^10.0.2", "axios": "^1.7.7", "body-parser": "^1.20.3", - "canyon-data": "0.1.1-alpha.8", + "canyon-data": "workspace:*", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dayjs": "^1.11.13", @@ -71,4 +71,4 @@ "tsconfig-paths": "^4.2.0", "typescript": "^5.6.3" } -} \ No newline at end of file +} diff --git a/packages/canyon-data/.gitignore b/packages/canyon-data/.gitignore new file mode 100755 index 00000000..57e646a1 --- /dev/null +++ b/packages/canyon-data/.gitignore @@ -0,0 +1,31 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +/coverage + + +pnpm-lock.yaml + +!src/coverage diff --git a/packages/canyon-data/package.json b/packages/canyon-data/package.json new file mode 100755 index 00000000..51097c35 --- /dev/null +++ b/packages/canyon-data/package.json @@ -0,0 +1,30 @@ +{ + "name": "canyon-data", + "version": "0.1.1-alpha.8", + "type": "module", + "main": "dist/canyon-data.cjs", + "module": "dist/canyon-data.js", + "types": "./dist/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "build": "vite build", + "prepare": "vite build", + "do-test": "vitest run --coverage" + }, + "devDependencies": { + "@types/istanbul-lib-coverage": "^2.0.5", + "@types/istanbul-lib-source-maps": "^4.0.4", + "@types/node": "^20.3.1", + "@vitest/coverage-v8": "^2.0.3", + "typescript": "^5.5.3", + "vite": "^5.3.4", + "vite-plugin-dts": "^3.5.3", + "vitest": "^2.0.3" + }, + "dependencies": { + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-source-maps": "^4.0.1" + } +} diff --git a/packages/canyon-data/src/coverage/index.ts b/packages/canyon-data/src/coverage/index.ts new file mode 100755 index 00000000..d6541aa2 --- /dev/null +++ b/packages/canyon-data/src/coverage/index.ts @@ -0,0 +1,108 @@ +// import libCoverage, {CoverageMapData} from "istanbul-lib-coverage"; +// import {formatCoverageData} from "../utils/formatCoverageData.ts"; +/** + * 合并两个覆盖率数据 + * @param first 第一个覆盖率数据 + * @param second 第二个覆盖率数据 + * @returns 合并过后的覆盖率数据 + */ +// export function mergeCoverageMap(first:CoverageMapData, second:CoverageMapData) { +// const map = libCoverage.createCoverageMap(JSON.parse(JSON.stringify(formatCoverageData(first)))); +// map.merge(formatCoverageData(second)); +// return JSON.parse(JSON.stringify(map.toJSON())); +// } + + + +/** + * 合并两个相同文件的文件覆盖对象实例,确保执行计数正确。 + * + * @method mergeFileCoverage + * @static + * @param {Object} first 给定文件的第一个文件覆盖对象 + * @param {Object} second 相同文件的第二个文件覆盖对象 + * @return {Object} 合并后的结果对象。请注意,输入对象不会被修改。 + */ +function mergeFileCoverage(first:any, second:any) { + first = { + b:{}, + f:{}, + s:{}, + ...first + } + second = { + b:{}, + f:{}, + s:{}, + ...second + } + const ret = JSON.parse(JSON.stringify(first)); + + delete ret.l; // 移除派生信息 + + Object.keys(second.s).forEach(function (k) { + if (ret.s[k]===undefined){ + ret.s[k] = second.s[k]; + } else { + ret.s[k] += second.s[k]; + } + }); + + Object.keys(second.f).forEach(function (k) { + if (ret.f[k]===undefined){ + ret.f[k] = second.f[k]; + } else { + ret.f[k] += second.f[k]; + } + }); + + Object.keys(second.b).forEach(function (k) { + if (ret.b[k]===undefined){ + ret.b[k] = JSON.parse(JSON.stringify(second.b[k])); + } else { + const retArray = ret.b[k]; + const secondArray = second.b[k]; + if (retArray){ + if (retArray.length>0){ + for (let i = 0; i < retArray.length; i += 1) { + retArray[i] += secondArray[i]; + } + } + } + } + }); + + return ret; +} + +/** + * 合并两个覆盖对象,确保执行计数正确。 + * + * @method mergeCoverage + * @static + * @param {Object} first 第一个覆盖对象 + * @param {Object} second 第二个覆盖对象 + * @return {Object} 合并后的结果对象。请注意,输入对象不会被修改。 + */ +export function mergeCoverageMap(first:any, second:any) { + if (!second) { + return first; + } + + const mergedCoverage = JSON.parse(JSON.stringify(first)); // 深拷贝 coverage,这样修改出来的是两个的合集 + Object.keys(second).forEach(function (filePath) { + const original = first[filePath]; + const added = second[filePath]; + let result; + + if (original) { + result = mergeFileCoverage(original, added); + } else { + result = added; + } + + mergedCoverage[filePath] = result; + }); + + return mergedCoverage; +} diff --git a/packages/canyon-data/src/coverage/test/coverage.test.ts b/packages/canyon-data/src/coverage/test/coverage.test.ts new file mode 100755 index 00000000..b7d9101f --- /dev/null +++ b/packages/canyon-data/src/coverage/test/coverage.test.ts @@ -0,0 +1,9 @@ +import { expect, test } from 'vitest' +import mockCoverageData from './mock-coverage-data.json' +import mockCoverageData1 from './mock-coverage-data1.json' +import mockCoverageDataMerged from './mock-coverage-data-merged.json' + +import {mergeCoverageMap} from "../index.ts"; +test('测试mergeCoverageMap方法', () => { + expect(mergeCoverageMap(mockCoverageData,mockCoverageData1)).toMatchObject(mockCoverageDataMerged) +}) diff --git a/packages/canyon-data/src/coverage/test/mock-coverage-data-merged.json b/packages/canyon-data/src/coverage/test/mock-coverage-data-merged.json new file mode 100755 index 00000000..1e69eb6d --- /dev/null +++ b/packages/canyon-data/src/coverage/test/mock-coverage-data-merged.json @@ -0,0 +1,536 @@ +{ + "/builds/canyon/canyon-demo/src/pages/Welcome.tsx": { + "path": "/builds/canyon/canyon-demo/src/pages/Welcome.tsx", + "statementMap": { + "0": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 10, + "column": 1 + } + }, + "1": { + "start": { + "line": 4, + "column": 17 + }, + "end": { + "line": 4, + "column": 30 + } + }, + "2": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 9, + "column": 6 + } + }, + "3": { + "start": { + "line": 7, + "column": 8 + }, + "end": { + "line": 7, + "column": 20 + } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 3, + "column": 17 + } + }, + "loc": { + "start": { + "line": 3, + "column": 22 + }, + "end": { + "line": 10, + "column": 1 + } + }, + "line": 3 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { + "line": 6, + "column": 21 + }, + "end": { + "line": 6, + "column": 22 + } + }, + "loc": { + "start": { + "line": 6, + "column": 25 + }, + "end": { + "line": 8, + "column": 5 + } + }, + "line": 6 + } + }, + "branchMap": {}, + "s": { + "0": 1, + "1": 0, + "2": 0, + "3": 0 + }, + "f": { + "0": 0, + "1": 0 + }, + "b": {}, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "44a06ed36306fbbfd189234db0541dd6d0488999" + }, + "/builds/canyon/canyon-demo/src/pages/Home.tsx": { + "path": "/builds/canyon/canyon-demo/src/pages/Home.tsx", + "statementMap": { + "0": { + "start": { + "line": 4, + "column": 30 + }, + "end": { + "line": 4, + "column": 41 + } + }, + "1": { + "start": { + "line": 6, + "column": 4 + }, + "end": { + "line": 6, + "column": 27 + } + }, + "2": { + "start": { + "line": 8, + "column": 8 + }, + "end": { + "line": 13, + "column": 9 + } + }, + "3": { + "start": { + "line": 9, + "column": 12 + }, + "end": { + "line": 9, + "column": 39 + } + }, + "4": { + "start": { + "line": 11, + "column": 12 + }, + "end": { + "line": 11, + "column": 37 + } + }, + "5": { + "start": { + "line": 12, + "column": 12 + }, + "end": { + "line": 12, + "column": 31 + } + }, + "6": { + "start": { + "line": 15, + "column": 4 + }, + "end": { + "line": 35, + "column": 5 + } + }, + "7": { + "start": { + "line": 18, + "column": 64 + }, + "end": { + "line": 18, + "column": 94 + } + }, + "8": { + "start": { + "line": 18, + "column": 84 + }, + "end": { + "line": 18, + "column": 93 + } + }, + "9": { + "start": { + "line": 24, + "column": 20 + }, + "end": { + "line": 24, + "column": 53 + } + }, + "10": { + "start": { + "line": 29, + "column": 20 + }, + "end": { + "line": 29, + "column": 43 + } + } + }, + "fnMap": { + "0": { + "name": "Home", + "decl": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "loc": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 36, + "column": 1 + } + }, + "line": 3 + }, + "1": { + "name": "tips", + "decl": { + "start": { + "line": 7, + "column": 13 + }, + "end": { + "line": 7, + "column": 17 + } + }, + "loc": { + "start": { + "line": 7, + "column": 28 + }, + "end": { + "line": 14, + "column": 5 + } + }, + "line": 7 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { + "line": 18, + "column": 58 + }, + "end": { + "line": 18, + "column": 59 + } + }, + "loc": { + "start": { + "line": 18, + "column": 64 + }, + "end": { + "line": 18, + "column": 94 + } + }, + "line": 18 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { + "line": 18, + "column": 73 + }, + "end": { + "line": 18, + "column": 74 + } + }, + "loc": { + "start": { + "line": 18, + "column": 84 + }, + "end": { + "line": 18, + "column": 93 + } + }, + "line": 18 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { + "line": 23, + "column": 45 + }, + "end": { + "line": 23, + "column": 46 + } + }, + "loc": { + "start": { + "line": 23, + "column": 49 + }, + "end": { + "line": 25, + "column": 17 + } + }, + "line": 23 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { + "line": 28, + "column": 46 + }, + "end": { + "line": 28, + "column": 47 + } + }, + "loc": { + "start": { + "line": 28, + "column": 50 + }, + "end": { + "line": 30, + "column": 17 + } + }, + "line": 28 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { + "line": 8, + "column": 8 + }, + "end": { + "line": 13, + "column": 9 + } + }, + "type": "if", + "locations": [ + { + "start": { + "line": 8, + "column": 8 + }, + "end": { + "line": 13, + "column": 9 + } + }, + { + "start": { + "line": 10, + "column": 15 + }, + "end": { + "line": 13, + "column": 9 + } + } + ], + "line": 8 + } + }, + "s": { + "0": 2, + "1": 2, + "2": 2, + "3": 0, + "4": 2, + "5": 2, + "6": 2, + "7": 0, + "8": 0, + "9": 0, + "10": 0 + }, + "f": { + "0": 2, + "1": 2, + "2": 0, + "3": 0, + "4": 0, + "5": 0 + }, + "b": { + "0": [ + 0, + 2 + ] + }, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "94acebb40f91fa6aab2be22d5f32d9b020aefe85" + }, + "/builds/canyon/canyon-demo/src/routers/index.tsx": { + "path": "/builds/canyon/canyon-demo/src/routers/index.tsx", + "statementMap": {}, + "fnMap": {}, + "branchMap": {}, + "s": {}, + "f": {}, + "b": {}, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "331bbffdd38304ac26edcd0f159b967d23f5bdcd" + }, + "/builds/canyon/canyon-demo/src/App.tsx": { + "path": "/builds/canyon/canyon-demo/src/App.tsx", + "statementMap": { + "0": { + "start": { + "line": 7, + "column": 26 + }, + "end": { + "line": 7, + "column": 49 + } + }, + "1": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 12, + "column": 3 + } + } + }, + "fnMap": { + "0": { + "name": "App", + "decl": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 12 + } + }, + "loc": { + "start": { + "line": 6, + "column": 15 + }, + "end": { + "line": 13, + "column": 1 + } + }, + "line": 6 + } + }, + "branchMap": {}, + "s": { + "0": 1, + "1": 1 + }, + "f": { + "0": 1 + }, + "b": {}, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "1454065cc7fb2c09d5dcdbb82b046335a719923d" + }, + "/builds/canyon/canyon-demo/src/main.tsx": { + "path": "/builds/canyon/canyon-demo/src/main.tsx", + "statementMap": { + "0": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 12, + "column": 1 + } + } + }, + "fnMap": {}, + "branchMap": {}, + "s": { + "0": 1 + }, + "f": {}, + "b": {}, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "2ca7e398b7e7ea1ee9480ada2f5111a10d111593" + } +} diff --git a/packages/canyon-data/src/coverage/test/mock-coverage-data.json b/packages/canyon-data/src/coverage/test/mock-coverage-data.json new file mode 100755 index 00000000..c27ea05e --- /dev/null +++ b/packages/canyon-data/src/coverage/test/mock-coverage-data.json @@ -0,0 +1,427 @@ +{ + "/builds/canyon/canyon-demo/src/pages/Home.tsx": { + "path": "/builds/canyon/canyon-demo/src/pages/Home.tsx", + "statementMap": { + "0": { + "start": { + "line": 4, + "column": 30 + }, + "end": { + "line": 4, + "column": 41 + } + }, + "1": { + "start": { + "line": 6, + "column": 4 + }, + "end": { + "line": 6, + "column": 27 + } + }, + "2": { + "start": { + "line": 8, + "column": 8 + }, + "end": { + "line": 13, + "column": 9 + } + }, + "3": { + "start": { + "line": 9, + "column": 12 + }, + "end": { + "line": 9, + "column": 39 + } + }, + "4": { + "start": { + "line": 11, + "column": 12 + }, + "end": { + "line": 11, + "column": 37 + } + }, + "5": { + "start": { + "line": 12, + "column": 12 + }, + "end": { + "line": 12, + "column": 31 + } + }, + "6": { + "start": { + "line": 15, + "column": 4 + }, + "end": { + "line": 35, + "column": 5 + } + }, + "7": { + "start": { + "line": 18, + "column": 64 + }, + "end": { + "line": 18, + "column": 94 + } + }, + "8": { + "start": { + "line": 18, + "column": 84 + }, + "end": { + "line": 18, + "column": 93 + } + }, + "9": { + "start": { + "line": 24, + "column": 20 + }, + "end": { + "line": 24, + "column": 53 + } + }, + "10": { + "start": { + "line": 29, + "column": 20 + }, + "end": { + "line": 29, + "column": 43 + } + } + }, + "fnMap": { + "0": { + "name": "Home", + "decl": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "loc": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 36, + "column": 1 + } + }, + "line": 3 + }, + "1": { + "name": "tips", + "decl": { + "start": { + "line": 7, + "column": 13 + }, + "end": { + "line": 7, + "column": 17 + } + }, + "loc": { + "start": { + "line": 7, + "column": 28 + }, + "end": { + "line": 14, + "column": 5 + } + }, + "line": 7 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { + "line": 18, + "column": 58 + }, + "end": { + "line": 18, + "column": 59 + } + }, + "loc": { + "start": { + "line": 18, + "column": 64 + }, + "end": { + "line": 18, + "column": 94 + } + }, + "line": 18 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { + "line": 18, + "column": 73 + }, + "end": { + "line": 18, + "column": 74 + } + }, + "loc": { + "start": { + "line": 18, + "column": 84 + }, + "end": { + "line": 18, + "column": 93 + } + }, + "line": 18 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { + "line": 23, + "column": 45 + }, + "end": { + "line": 23, + "column": 46 + } + }, + "loc": { + "start": { + "line": 23, + "column": 49 + }, + "end": { + "line": 25, + "column": 17 + } + }, + "line": 23 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { + "line": 28, + "column": 46 + }, + "end": { + "line": 28, + "column": 47 + } + }, + "loc": { + "start": { + "line": 28, + "column": 50 + }, + "end": { + "line": 30, + "column": 17 + } + }, + "line": 28 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { + "line": 8, + "column": 8 + }, + "end": { + "line": 13, + "column": 9 + } + }, + "type": "if", + "locations": [ + { + "start": { + "line": 8, + "column": 8 + }, + "end": { + "line": 13, + "column": 9 + } + }, + { + "start": { + "line": 10, + "column": 15 + }, + "end": { + "line": 13, + "column": 9 + } + } + ], + "line": 8 + } + }, + "s": { + "0": 1, + "1": 1, + "2": 1, + "3": 0, + "4": 1, + "5": 1, + "6": 1, + "7": 0, + "8": 0, + "9": 0, + "10": 0 + }, + "f": { + "0": 1, + "1": 1, + "2": 0, + "3": 0, + "4": 0, + "5": 0 + }, + "b": { + "0": [ + 0, + 1 + ] + }, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "94acebb40f91fa6aab2be22d5f32d9b020aefe85" + }, + "/builds/canyon/canyon-demo/src/routers/index.tsx": { + "path": "/builds/canyon/canyon-demo/src/routers/index.tsx", + "statementMap": {}, + "fnMap": {}, + "branchMap": {}, + "s": {}, + "f": {}, + "b": {}, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "331bbffdd38304ac26edcd0f159b967d23f5bdcd" + }, + "/builds/canyon/canyon-demo/src/App.tsx": { + "path": "/builds/canyon/canyon-demo/src/App.tsx", + "statementMap": { + "0": { + "start": { + "line": 7, + "column": 26 + }, + "end": { + "line": 7, + "column": 49 + } + }, + "1": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 12, + "column": 3 + } + } + }, + "fnMap": { + "0": { + "name": "App", + "decl": { + "start": { + "line": 6, + "column": 9 + }, + "end": { + "line": 6, + "column": 12 + } + }, + "loc": { + "start": { + "line": 6, + "column": 15 + }, + "end": { + "line": 13, + "column": 1 + } + }, + "line": 6 + } + }, + "branchMap": {}, + "s": { + "0": 1, + "1": 1 + }, + "f": { + "0": 1 + }, + "b": {}, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "1454065cc7fb2c09d5dcdbb82b046335a719923d" + }, + "/builds/canyon/canyon-demo/src/main.tsx": { + "path": "/builds/canyon/canyon-demo/src/main.tsx", + "statementMap": { + "0": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 12, + "column": 1 + } + } + }, + "fnMap": {}, + "branchMap": {}, + "s": { + "0": 1 + }, + "f": {}, + "b": {}, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "2ca7e398b7e7ea1ee9480ada2f5111a10d111593" + } +} diff --git a/packages/canyon-data/src/coverage/test/mock-coverage-data1.json b/packages/canyon-data/src/coverage/test/mock-coverage-data1.json new file mode 100755 index 00000000..84c3d7b1 --- /dev/null +++ b/packages/canyon-data/src/coverage/test/mock-coverage-data1.json @@ -0,0 +1,450 @@ +{ + "/builds/canyon/canyon-demo/src/pages/Welcome.tsx": { + "path": "/builds/canyon/canyon-demo/src/pages/Welcome.tsx", + "statementMap": { + "0": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 10, + "column": 1 + } + }, + "1": { + "start": { + "line": 4, + "column": 17 + }, + "end": { + "line": 4, + "column": 30 + } + }, + "2": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 9, + "column": 6 + } + }, + "3": { + "start": { + "line": 7, + "column": 8 + }, + "end": { + "line": 7, + "column": 20 + } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 3, + "column": 17 + } + }, + "loc": { + "start": { + "line": 3, + "column": 22 + }, + "end": { + "line": 10, + "column": 1 + } + }, + "line": 3 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { + "line": 6, + "column": 21 + }, + "end": { + "line": 6, + "column": 22 + } + }, + "loc": { + "start": { + "line": 6, + "column": 25 + }, + "end": { + "line": 8, + "column": 5 + } + }, + "line": 6 + } + }, + "branchMap": {}, + "s": { + "0": 1, + "1": 0, + "2": 0, + "3": 0 + }, + "f": { + "0": 0, + "1": 0 + }, + "b": {}, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "44a06ed36306fbbfd189234db0541dd6d0488999" + }, + "/builds/canyon/canyon-demo/src/pages/Home.tsx": { + "path": "/builds/canyon/canyon-demo/src/pages/Home.tsx", + "statementMap": { + "0": { + "start": { + "line": 4, + "column": 30 + }, + "end": { + "line": 4, + "column": 41 + } + }, + "1": { + "start": { + "line": 6, + "column": 4 + }, + "end": { + "line": 6, + "column": 27 + } + }, + "2": { + "start": { + "line": 8, + "column": 8 + }, + "end": { + "line": 13, + "column": 9 + } + }, + "3": { + "start": { + "line": 9, + "column": 12 + }, + "end": { + "line": 9, + "column": 39 + } + }, + "4": { + "start": { + "line": 11, + "column": 12 + }, + "end": { + "line": 11, + "column": 37 + } + }, + "5": { + "start": { + "line": 12, + "column": 12 + }, + "end": { + "line": 12, + "column": 31 + } + }, + "6": { + "start": { + "line": 15, + "column": 4 + }, + "end": { + "line": 35, + "column": 5 + } + }, + "7": { + "start": { + "line": 18, + "column": 64 + }, + "end": { + "line": 18, + "column": 94 + } + }, + "8": { + "start": { + "line": 18, + "column": 84 + }, + "end": { + "line": 18, + "column": 93 + } + }, + "9": { + "start": { + "line": 24, + "column": 20 + }, + "end": { + "line": 24, + "column": 53 + } + }, + "10": { + "start": { + "line": 29, + "column": 20 + }, + "end": { + "line": 29, + "column": 43 + } + } + }, + "fnMap": { + "0": { + "name": "Home", + "decl": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "loc": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 36, + "column": 1 + } + }, + "line": 3 + }, + "1": { + "name": "tips", + "decl": { + "start": { + "line": 7, + "column": 13 + }, + "end": { + "line": 7, + "column": 17 + } + }, + "loc": { + "start": { + "line": 7, + "column": 28 + }, + "end": { + "line": 14, + "column": 5 + } + }, + "line": 7 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { + "line": 18, + "column": 58 + }, + "end": { + "line": 18, + "column": 59 + } + }, + "loc": { + "start": { + "line": 18, + "column": 64 + }, + "end": { + "line": 18, + "column": 94 + } + }, + "line": 18 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { + "line": 18, + "column": 73 + }, + "end": { + "line": 18, + "column": 74 + } + }, + "loc": { + "start": { + "line": 18, + "column": 84 + }, + "end": { + "line": 18, + "column": 93 + } + }, + "line": 18 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { + "line": 23, + "column": 45 + }, + "end": { + "line": 23, + "column": 46 + } + }, + "loc": { + "start": { + "line": 23, + "column": 49 + }, + "end": { + "line": 25, + "column": 17 + } + }, + "line": 23 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { + "line": 28, + "column": 46 + }, + "end": { + "line": 28, + "column": 47 + } + }, + "loc": { + "start": { + "line": 28, + "column": 50 + }, + "end": { + "line": 30, + "column": 17 + } + }, + "line": 28 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { + "line": 8, + "column": 8 + }, + "end": { + "line": 13, + "column": 9 + } + }, + "type": "if", + "locations": [ + { + "start": { + "line": 8, + "column": 8 + }, + "end": { + "line": 13, + "column": 9 + } + }, + { + "start": { + "line": 10, + "column": 15 + }, + "end": { + "line": 13, + "column": 9 + } + } + ], + "line": 8 + } + }, + "s": { + "0": 1, + "1": 1, + "2": 1, + "3": 0, + "4": 1, + "5": 1, + "6": 1, + "7": 0, + "8": 0, + "9": 0, + "10": 0 + }, + "f": { + "0": 1, + "1": 1, + "2": 0, + "3": 0, + "4": 0, + "5": 0 + }, + "b": { + "0": [ + 0, + 1 + ] + }, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "94acebb40f91fa6aab2be22d5f32d9b020aefe85" + }, + "/builds/canyon/canyon-demo/src/routers/index.tsx": { + "path": "/builds/canyon/canyon-demo/src/routers/index.tsx", + "statementMap": {}, + "fnMap": {}, + "branchMap": {}, + "s": {}, + "f": {}, + "b": {}, + "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", + "hash": "331bbffdd38304ac26edcd0f159b967d23f5bdcd" + } +} diff --git a/packages/canyon-data/src/index.ts b/packages/canyon-data/src/index.ts new file mode 100755 index 00000000..cff738fa --- /dev/null +++ b/packages/canyon-data/src/index.ts @@ -0,0 +1,2 @@ +export * from "./coverage" +export * from "./summary" diff --git a/packages/canyon-data/src/summary/helpers.ts b/packages/canyon-data/src/summary/helpers.ts new file mode 100755 index 00000000..0257b0fb --- /dev/null +++ b/packages/canyon-data/src/summary/helpers.ts @@ -0,0 +1,32 @@ +export const emptySummary = { + functions:{ + covered: 0, + total: 0, + skipped: 0, + pct: 0, + }, + statements:{ + covered: 0, + total: 0, + skipped: 0, + pct: 0, + }, + branches:{ + covered: 0, + total: 0, + skipped: 0, + pct: 0, + }, + lines:{ + covered: 0, + total: 0, + skipped: 0, + pct: 0, + }, + newlines:{ + covered: 0, + total: 0, + skipped: 0, + pct: 0, + }, +} diff --git a/packages/canyon-data/src/summary/index.ts b/packages/canyon-data/src/summary/index.ts new file mode 100755 index 00000000..f138215a --- /dev/null +++ b/packages/canyon-data/src/summary/index.ts @@ -0,0 +1,134 @@ +import {percent} from "../utils/percent.ts"; +import libCoverage, {CoverageMapData, CoverageSummaryData, Totals} from "istanbul-lib-coverage"; +import {calculateNewLineCoverageForSingleFile} from "../utils/line.ts"; +import {emptySummary} from "./helpers.ts"; +import {formatCoverageData} from "../utils/formatCoverageData.ts"; +export interface CodeChange{ + path:string + additions:number[] +} +export interface CoverageSummaryDataMap { + [key: string]: CoverageSummaryData&{newlines:Totals}; +} + +/** + * 合并两个概要数据 + * @param first 第一个概要数据 + * @param second 第二个概要数据 + * @returns 合并过后的概要数据 + */ +export function mergeSummary(first:any,second:any):any { + const ret = JSON.parse(JSON.stringify(first)); + const keys = [ + 'lines', + 'statements', + 'branches', + 'functions', + 'branchesTrue', + 'newlines' + ]; + keys.forEach(key => { + if (second[key]) { + ret[key].total += second[key].total; + ret[key].covered += second[key].covered; + ret[key].skipped += second[key].skipped; + ret[key].pct = percent(ret[key].covered, ret[key].total); + } + }); + + return ret; +} + + +export const genSummaryMapByCoverageMap = (coverageMapData: CoverageMapData,codeChanges?:CodeChange[]):CoverageSummaryDataMap => { + const summaryMap: any = {}; + const m = libCoverage.createCoverageMap(formatCoverageData(coverageMapData)); + m.files().forEach(function (f) { + const fc = m.fileCoverageFor(f), + s = fc.toSummary(); + summaryMap[f] = { + ...s.data, + newlines:calculateNewLineCoverageForSingleFile(fc.data,codeChanges?.find(c=>`${c.path}`===f)?.additions||[]) + }; + }); + return JSON.parse(JSON.stringify(summaryMap)); +} + +export const getSummaryByPath = ( + path: string, + summary: CoverageSummaryDataMap, +) => { + let summaryObj = JSON.parse(JSON.stringify(emptySummary)); + const filterSummary = Object.keys(summary).reduce((pre: any, cur) => { + if (cur.indexOf(path) === 0) { + pre[cur] = summary[cur]; + } + return pre; + }, {}); + + Object.keys(filterSummary).forEach((item) => { + summaryObj = mergeSummary(summaryObj,filterSummary[item]); + }); + return JSON.parse(JSON.stringify(summaryObj)); +}; + +// summary 是总的 +export const genSummaryTreeItem = ( + path: string, + summary: CoverageSummaryDataMap, +):{ + path: string; + summary: CoverageSummaryData; + children: { + path: string; + summary: CoverageSummaryData; + }[]; +} => { + function check(item: string, path: string) { + if (path === '~') { + return true; + } + return item.startsWith(path); + } + + // 如果是文件 + if (Object.keys(summary).find((item) => item === path)) { + return { + path, + summary: getSummaryByPath(path, summary), + children: [], + }; + } + // 如果是文件夹 + const fileLists: string[] = []; + const folderLists: string[] = []; + + Object.keys(summary).forEach((item) => { + const newpath = path === '' ? item : item.replace(path + '/', ''); + if (check(item, path) && !newpath.includes('/')) { + fileLists.push(item); + } + if (check(item, path) && newpath.includes('/')) { + folderLists.push((path === '' ? '' : path + '/') + newpath.split('/')[0]); + } + }); + + return { + path, + summary: getSummaryByPath(path, summary), + children: [ + ...[...new Set(fileLists)].map((item) => { + return { + path: item, + summary: getSummaryByPath(item, summary), + }; + }), + ...[...new Set(folderLists)].map((item) => { + return { + path: item, + summary: getSummaryByPath(item, summary), + }; + }), + ], + }; +}; diff --git a/packages/canyon-data/src/utils/formatCoverageData.ts b/packages/canyon-data/src/utils/formatCoverageData.ts new file mode 100644 index 00000000..8c6df0f3 --- /dev/null +++ b/packages/canyon-data/src/utils/formatCoverageData.ts @@ -0,0 +1,13 @@ +export const formatCoverageData = (coverageData:any):any=>{ + const obj:any = {} + Object.entries(coverageData).forEach(([key, value]:any)=>{ + obj[key] = { + path: key, + branchMap:{}, + statementMap:{}, + fnMap:{}, + ...value + } + }) + return obj +} diff --git a/packages/canyon-data/src/utils/line.ts b/packages/canyon-data/src/utils/line.ts new file mode 100755 index 00000000..f4186233 --- /dev/null +++ b/packages/canyon-data/src/utils/line.ts @@ -0,0 +1,40 @@ +import {FileCoverageData, Range} from "istanbul-lib-coverage"; +import {percent} from "./percent.ts"; + +/** + * returns computed line coverage from statement coverage. + * This is a map of hits keyed by line number in the source. + */ +function getLineCoverage(statementMap:{ [key: string]: Range },s:{ [key: string]: number }) { + const statements = s; + const lineMap = Object.create(null); + + Object.entries(statements).forEach(([st, count]) => { + if (!statementMap[st]) { + return; + } + const { line } = statementMap[st].start; + const prevVal = lineMap[line]; + if (prevVal === undefined || prevVal < count) { + lineMap[line] = count; + } + }); + return lineMap; +} + + +export function calculateNewLineCoverageForSingleFile(coverage:FileCoverageData, newLine:number[]) { + const lineStats = getLineCoverage(coverage.statementMap,coverage.s); + const rows:[string,unknown][] = []; + Object.entries(lineStats).forEach(([lineNumber, count]) => { + if (newLine.includes(Number(lineNumber))) { + rows.push([lineNumber, count]); + } + }); + return { + total: newLine.length, + covered: newLine.length - rows.filter((i) => !i[1]).length, + skipped: 0, + pct: percent(newLine.length - rows.filter((i) => !i[1]).length, newLine.length) + }; +} diff --git a/packages/canyon-data/src/utils/percent.ts b/packages/canyon-data/src/utils/percent.ts new file mode 100755 index 00000000..21b076af --- /dev/null +++ b/packages/canyon-data/src/utils/percent.ts @@ -0,0 +1,9 @@ +export function percent(covered:number, total:number) { + let tmp; + if (total > 0) { + tmp = (1000 * 100 * covered) / total; + return Math.floor(tmp / 10) / 100; + } else { + return 100.0; + } +}; diff --git a/packages/canyon-data/tsconfig.json b/packages/canyon-data/tsconfig.json new file mode 100755 index 00000000..75abdef2 --- /dev/null +++ b/packages/canyon-data/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/packages/canyon-data/vite.config.ts b/packages/canyon-data/vite.config.ts new file mode 100755 index 00000000..ac8f4d06 --- /dev/null +++ b/packages/canyon-data/vite.config.ts @@ -0,0 +1,17 @@ +import { resolve } from "path" +import { defineConfig } from "vite" +import dts from 'vite-plugin-dts' + +export default defineConfig({ + plugins:[dts()], + build: { + outDir: "./dist", + emptyOutDir: true, + lib: { + entry: resolve(__dirname, "src/index.ts"), + fileName: "canyon-data", + formats: ["es", "cjs"], + }, + sourcemap: true, + }, +}) diff --git a/packages/canyon-platform/package.json b/packages/canyon-platform/package.json index d697e8c4..01b6efa4 100644 --- a/packages/canyon-platform/package.json +++ b/packages/canyon-platform/package.json @@ -19,7 +19,7 @@ "ahooks": "^3.8.1", "antd": "^5.21.4", "axios": "^1.7.7", - "canyon-data": "^0.1.1-alpha.8", + "canyon-data": "workspace:*", "clsx": "^2.1.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.13", @@ -71,4 +71,4 @@ "vite": "^5.4.9", "vite-plugin-pages": "^0.32.3" } -} \ No newline at end of file +} diff --git a/packages/canyon-platform/vite.config.ts b/packages/canyon-platform/vite.config.ts index 13758d11..d750d2ee 100644 --- a/packages/canyon-platform/vite.config.ts +++ b/packages/canyon-platform/vite.config.ts @@ -38,6 +38,7 @@ export default defineConfig({ alias: { "@": path.resolve(__dirname, "./src"), "canyon-report": path.resolve(__dirname, "../canyon-report"), + "canyon-data": path.resolve(__dirname, "../canyon-data/src"), }, }, build: { diff --git a/packages/canyon-report/package.json b/packages/canyon-report/package.json index db6bae2f..4e3e8d33 100755 --- a/packages/canyon-report/package.json +++ b/packages/canyon-report/package.json @@ -21,7 +21,7 @@ "@ant-design/icons": "^5.5.1", "antd": "^5.21.4", "axios": "^1.7.7", - "canyon-data": "0.1.1-alpha.8", + "canyon-data": "workspace:*", "commander": "^12.1.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -68,4 +68,4 @@ "webpack-dev-server": "^5.1.0", "jest": "^29.7.0" } -} \ No newline at end of file +}