diff --git a/src/filename-utils.test.ts b/src/filename-utils.test.ts new file mode 100644 index 0000000..1c22896 --- /dev/null +++ b/src/filename-utils.test.ts @@ -0,0 +1,47 @@ +import { escapeMarkdownCharacters, getPrettyPathName } from "./filename-utils" +describe("getPrettyPathName", () => { + it("doesn't change strings equal to the limit", () => { + const input = "/exactly/17/chars" + const output = getPrettyPathName(input, 17) + expect(output).toEqual(input) + }) + + it("elides the middle directories first", () => { + const input = "/just/over/the/limit" + const output = getPrettyPathName(input, 17) + expect(output).toEqual("/just/../limit") + }) + + it("elides the middle directories from right to left, excluding the root directory", () => { + const input = "/just/over/the/limit" + const output = getPrettyPathName(input, 18) + expect(output).toEqual("/just/../the/limit") + }) + + it("elides the root directory second", () => { + const input = "/quite-a-lot-actually/over/the/limit" + const output = getPrettyPathName(input, 17) + expect(output).toEqual("../limit") + }) + + it("truncates the the basename third", () => { + const input = "/quite-a-lot-actually/over/the/limit-and-this-is-long-as-well" + const output = getPrettyPathName(input, 17) + expect(output).toEqual("../limit-and-th..") + }) + + it("treats the tilde as the root directory", () => { + const input = "~/just/over/the/limit" + const output = getPrettyPathName(input, 18) + expect(output).toEqual("~/../the/limit") + }) +}) + +describe("escapeMarkdownCharacters", () => { + it("escapes filenames with '|,(,),[,],#,*,{,},-,+,_,!,\\,`>' characters", () => { + const filename = `src/file-with-characters{[(|#*-+_!\`)]}.ts` + const expectedFilename = `src/file\\-with\\-characters\\{\\[\\(\\|\\#\\*\\-\\+\\_\\!\\\`\\)\\]\\}.ts` + const output = escapeMarkdownCharacters(filename) + expect(output).toEqual(expectedFilename) + }) +}) diff --git a/src/filename-utils.ts b/src/filename-utils.ts new file mode 100644 index 0000000..67dc88a --- /dev/null +++ b/src/filename-utils.ts @@ -0,0 +1,53 @@ +import * as _ from "lodash" +import * as path from "path" + +/** + * Shortens the length of a directory in a pretty way. + * @param pathName The path to shorten + * @param maxLength The maximum length of the path. Must be at least 4. + * @returns The shortened directory name. + */ +export function getPrettyPathName(pathName: string, maxLength: number): string { + if (maxLength < 4) { + throw Error("maxLength must be at least 4") + } + if (pathName.length <= maxLength) { + return pathName + } + + const parts = path.parse(pathName) + + const dirWithoutRoot = parts.dir.slice(parts.root.length, parts.dir.length - parts.root.length + 1) + const dirs = dirWithoutRoot.split(path.sep) + const root = `${parts.root}..${path.sep}` + + // Save the first directory, we want to try and prioritize keeping it in the path. + let firstDir = dirs.shift() + firstDir = firstDir ? firstDir : "" + + while (dirs.length > 0) { + // Except for the first directory, start removing dirs from left to right. + dirs.shift() + const middle = ["..", ...dirs].join(path.sep) + const newPath = `${parts.root}${firstDir}${path.sep}${middle}${path.sep}${parts.base}` + if (newPath.length <= maxLength) { + return newPath + } + } + + const rootAndName = `..${path.sep}${parts.base}` + if (rootAndName.length <= maxLength) { + return rootAndName + } + return `${rootAndName.slice(0, maxLength - 2)}..` +} + +/** + * Escapes the characters |()[]#*{}-+_!\,`> from a string. + * @param source The source to escape + * @returns An escaped version of the string + */ +export function escapeMarkdownCharacters(source: string) { + const escapedCharacters = ["|", "(", ")", "[", "]", "#", "*", "{", "}", "-", "+", "_", "!", "\\", "`"] + return [...source].map(c => (_.includes(escapedCharacters, c) ? `\\${c}` : c)).join("") +} diff --git a/src/git-utils.ts b/src/git-utils.ts new file mode 100644 index 0000000..78ff5c8 --- /dev/null +++ b/src/git-utils.ts @@ -0,0 +1,3 @@ +export function getGitRoot(): Promise { + throw Error("Unimplemented") +} diff --git a/src/index.test.ts b/src/index.test.ts index 71eef7e..7dcd163 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -9,15 +9,20 @@ const basePath = "/some/random/path/to/repo" function makeCoverageEntry(coverage: number) { return `{ - "0": ${ coverage < 25 ? 0 : 1 }, - "1": ${ coverage < 50 ? 0 : 1}, - "2": ${ coverage < 75 ? 0 : 1 }, - "3": ${ coverage < 100 ? 0 : 1 } + "0": ${coverage < 25 ? 0 : 1}, + "1": ${coverage < 50 ? 0 : 1}, + "2": ${coverage < 75 ? 0 : 1}, + "3": ${coverage < 100 ? 0 : 1} }` } -function makeEntry(fileName: string, lineCoverage = 100, statementCoverage = 100, functionCoverage = 100, - branchCoverage = 100) { +function makeEntry( + fileName: string, + lineCoverage = 100, + statementCoverage = 100, + functionCoverage = 100, + branchCoverage = 100 +) { return ` "${fileName}": { "lines": { "total": 100, "covered": ${lineCoverage}, "skipped": 0, "pct": ${lineCoverage} }, @@ -29,10 +34,10 @@ function makeEntry(fileName: string, lineCoverage = 100, statementCoverage = 100 } function setupCoverageFile(coverage?: string) { - (FilesystemService as any).mockImplementation( () => { + ;(FilesystemService as any).mockImplementation(() => { return { - exists: (p) => coverage !== undefined, - read: (p) => { + exists: p => coverage !== undefined, + read: p => { return coverage !== undefined ? Buffer.from(coverage, "utf8") : undefined }, } @@ -40,7 +45,6 @@ function setupCoverageFile(coverage?: string) { } describe("istanbulCoverage()", () => { - beforeEach(() => { global.warn = jest.fn() global.message = jest.fn() @@ -48,14 +52,8 @@ describe("istanbulCoverage()", () => { global.markdown = jest.fn() global.danger = { git: { - modified_files: [ - "src/modified-file1.ts", - "src/modified-file2.ts", - ], - created_files: [ - "src/created-file1.ts", - "src/created-file2.ts", - ], + modified_files: ["src/modified-file1.ts", "src/modified-file2.ts"], + created_files: ["src/created-file1.ts", "src/created-file2.ts"], }, } setupCoverageFile(`{ @@ -76,41 +74,41 @@ describe("istanbulCoverage()", () => { jest.resetAllMocks() }) - it("will only report on new files when reportFileSet is set to \"created\"", () => { + it('will only report on new files when reportFileSet is set to "created"', () => { istanbulCoverage({ reportFileSet: "created", }) expect(global.markdown).toHaveBeenCalledWith( -`## Coverage in New Files + `## Coverage in New Files File | Line Coverage | Statement Coverage | Function Coverage | Branch Coverage ---- | ------------: | -----------------: | ----------------: | --------------: [src/created\\-file1.ts](src/created\\-file1.ts) | (66/100) 66% | (100/100) 100% | (25/100) 25% | (50/100) 50% [src/created\\-file2.ts](src/created\\-file2.ts) | (99/100) 99% | (75/100) 75% | (50/100) 50% | (25/100) 25% Total | (165/200) 83% | (175/200) 88% | (75/200) 38% | (75/200) 38% -`, +` ) }) - it("will only report on modified files when reportFileSet is set to \"modified\"", () => { + it('will only report on modified files when reportFileSet is set to "modified"', () => { istanbulCoverage({ reportFileSet: "modified", }) expect(global.markdown).toHaveBeenCalledWith( -`## Coverage in Modified Files + `## Coverage in Modified Files File | Line Coverage | Statement Coverage | Function Coverage | Branch Coverage ---- | ------------: | -----------------: | ----------------: | --------------: [src/modified\\-file1.ts](src/modified\\-file1.ts) | (66/100) 66% | (25/100) 25% | (25/100) 25% | (25/100) 25% [src/modified\\-file2.ts](src/modified\\-file2.ts) | (99/100) 99% | (50/100) 50% | (75/100) 75% | (50/100) 50% Total | (165/200) 83% | (75/200) 38% | (100/200) 50% | (75/200) 38% -`, +` ) }) - it("will only report on created and modified files when reportFileSet is set to \"createdOrModified\"", () => { + it('will only report on created and modified files when reportFileSet is set to "createdOrModified"', () => { istanbulCoverage({ reportFileSet: "createdOrModified", }) expect(global.markdown).toHaveBeenCalledWith( -`## Coverage in Created or Modified Files + `## Coverage in Created or Modified Files File | Line Coverage | Statement Coverage | Function Coverage | Branch Coverage ---- | ------------: | -----------------: | ----------------: | --------------: [src/created\\-file1.ts](src/created\\-file1.ts) | (66/100) 66% | (100/100) 100% | (25/100) 25% | (50/100) 50% @@ -118,16 +116,16 @@ File | Line Coverage | Statement Coverage | Function Coverage | Branch Coverage [src/modified\\-file1.ts](src/modified\\-file1.ts) | (66/100) 66% | (25/100) 25% | (25/100) 25% | (25/100) 25% [src/modified\\-file2.ts](src/modified\\-file2.ts) | (99/100) 99% | (50/100) 50% | (75/100) 75% | (50/100) 50% Total | (330/400) 83% | (250/400) 63% | (175/400) 44% | (150/400) 38% -`, +` ) }) - it("will report all files when reportFileSet is set to \"all\"", () => { + it('will report all files when reportFileSet is set to "all"', () => { istanbulCoverage({ reportFileSet: "all", }) expect(global.markdown).toHaveBeenCalledWith( -`## Coverage in All Files + `## Coverage in All Files File | Line Coverage | Statement Coverage | Function Coverage | Branch Coverage ---- | ------------: | -----------------: | ----------------: | --------------: [src/created\\-file1.ts](src/created\\-file1.ts) | (66/100) 66% | (100/100) 100% | (25/100) 25% | (50/100) 50% @@ -136,7 +134,7 @@ File | Line Coverage | Statement Coverage | Function Coverage | Branch Coverage [src/modified\\-file2.ts](src/modified\\-file2.ts) | (99/100) 99% | (50/100) 50% | (75/100) 75% | (50/100) 50% [src/unmodified\\-field.ts](src/unmodified\\-field.ts) | (25/100) 25% | (25/100) 25% | (25/100) 25% | (25/100) 25% Total | (355/500) 71% | (275/500) 55% | (200/500) 40% | (175/500) 35% -`, +` ) }) @@ -179,7 +177,7 @@ Total | (355/500) 71% | (275/500) 55% | (200/500) 40% | (175/500) 35% }) expect(global.warn).not.toBeCalled() }) - it("doesn't output anything when reportFileSet is set to \"created\" and there are no created files ", () => { + it('doesn\'t output anything when reportFileSet is set to "created" and there are no created files ', () => { global.danger.git.created_files = [] istanbulCoverage({ reportMode: "fail", @@ -190,7 +188,7 @@ Total | (355/500) 71% | (275/500) 55% | (200/500) 40% | (175/500) 35% expect(global.message).not.toBeCalled() }) - it("doesn't output anything when reportFileSet is set to \"modified\" and there are no modified files ", () => { + it('doesn\'t output anything when reportFileSet is set to "modified" and there are no modified files ', () => { global.danger.git.modified_files = [] istanbulCoverage({ reportMode: "fail", @@ -223,22 +221,4 @@ Total | (355/500) 71% | (275/500) 55% | (200/500) 40% | (175/500) 35% }) expect(global.warn).toBeCalled() }) - - it("escapes filenames with '|,(,),[,],#,*,{,},-,+,_,!,\\,`>' characters", () => { - // Excapes the list of special markdown characters, mentioned here: - // https://daringfireball.net/projects/markdown/syntax#backslash - setupCoverageFile(`{ - ${makeEntry(`${__dirname}/src/file-with-characters{[(|#*-+_!\`)]}.ts`, 25, 25, 25, 25)} - }`) - istanbulCoverage() - const expectedFilename = `src/file\\-with\\-characters\\{\\[\\(\\|\\#\\*\\-\\+\\_\\!\\\`\\)\\]\\}.ts` - expect(global.markdown).toHaveBeenCalledWith( -`## Coverage in All Files -File | Line Coverage | Statement Coverage | Function Coverage | Branch Coverage ----- | ------------: | -----------------: | ----------------: | --------------: -[${expectedFilename}](${expectedFilename}) | (25/100) 25% | (25/100) 25% | (25/100) 25% | (25/100) 25% -Total | (25/100) 25% | (25/100) 25% | (25/100) 25% | (25/100) 25% -`, - ) - }) }) diff --git a/src/index.ts b/src/index.ts index 7b13102..fbb4f56 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ import { declare var danger: DangerDSLType import * as _ from "lodash" import * as path from "path" +import { escapeMarkdownCharacters, getPrettyPathName } from "./filename-utils" import FilesystemService from "./filesystem.service" export declare function message(message: string): void @@ -90,8 +91,7 @@ function formatItem(item: CoverageItem) { } function formatSourceName(source: string) { - const escapedCharacters = ["|", "(", ")", "[", "]", "#", "*", "{", "}", "-", "+", "_", "!", "\\", "`"] - return [...source].map(c => (_.includes(escapedCharacters, c) ? `\\${c}` : c)).join("") + return escapeMarkdownCharacters(getPrettyPathName(source, 30)) } function generateReport(coverage: CoverageModel, reportChangeType: ReportFileSet) { @@ -105,8 +105,9 @@ File | Line Coverage | Statement Coverage | Function Coverage | Branch Coverage .map(filename => { const e = coverage[filename] const shortFilename = formatSourceName(path.relative(__dirname, filename)) + const linkFilename = escapeMarkdownCharacters(path.relative(__dirname, filename)) return [ - `[${shortFilename}](${shortFilename})`, + `[${shortFilename}](${linkFilename})`, formatItem(e.lines), formatItem(e.statements), formatItem(e.functions),