generated from MetaMask/metamask-module-template
-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add project linting and reporting code
Once we have the ability to execute arbitrary rules on a known project, we now need to go up one level and add the ability to receive a reference to some kind of project (a shortname, like "utils", or a directory path), resolve that reference to a Git repository, clone the repository if necessary, execute the rules on that repository, and then print out a report. This code implements those sequence of steps.
- Loading branch information
Showing
10 changed files
with
476 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import execa from 'execa'; | ||
import { mockDeep } from 'jest-mock-extended'; | ||
import { MockWritable } from 'stdio-mock'; | ||
|
||
import type { MetaMaskRepository } from './establish-metamask-repository'; | ||
import type { Rule } from './execute-rules'; | ||
import { lintProject } from './lint-project'; | ||
import { OutputLogger } from './output-logger'; | ||
import { fakeDateOnly, withinSandbox } from '../tests/helpers'; | ||
import type { PrimaryExecaFunction } from '../tests/helpers'; | ||
import { setupToolWithMockRepositories } from '../tests/setup-tool-with-mock-repositories'; | ||
|
||
jest.mock('execa'); | ||
|
||
const execaMock = jest.mocked<PrimaryExecaFunction>(execa); | ||
|
||
describe('lintProject', () => { | ||
beforeEach(() => { | ||
fakeDateOnly(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('executes all of the given rules against the given project, calculating the time including and excluding linting', async () => { | ||
jest.setSystemTime(new Date('2023-01-01T00:00:00Z')); | ||
|
||
await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { | ||
const { cachedRepositoriesDirectoryPath } = | ||
await setupToolWithMockRepositories({ | ||
execaMock, | ||
sandboxDirectoryPath, | ||
repositories: [ | ||
{ | ||
name: 'some-project', | ||
}, | ||
], | ||
}); | ||
const template = mockDeep<MetaMaskRepository>(); | ||
const rules: Rule[] = [ | ||
{ | ||
name: 'rule-1', | ||
description: 'Description for rule 1', | ||
dependencies: ['rule-2'], | ||
execute: async () => { | ||
jest.setSystemTime(new Date('2023-01-01T00:00:02Z')); | ||
return { | ||
passed: false, | ||
failures: [{ message: 'Oops' }], | ||
}; | ||
}, | ||
}, | ||
{ | ||
name: 'rule-2', | ||
description: 'Description for rule 2', | ||
dependencies: [], | ||
execute: async () => { | ||
jest.setSystemTime(new Date('2023-01-01T00:00:01Z')); | ||
return { | ||
passed: true, | ||
}; | ||
}, | ||
}, | ||
]; | ||
const stdout = new MockWritable(); | ||
const stderr = new MockWritable(); | ||
const outputLogger = new OutputLogger({ stdout, stderr }); | ||
|
||
const projectLintResult = await lintProject({ | ||
projectReference: 'some-project', | ||
template, | ||
rules, | ||
workingDirectoryPath: sandboxDirectoryPath, | ||
cachedRepositoriesDirectoryPath, | ||
outputLogger, | ||
}); | ||
|
||
expect(projectLintResult).toStrictEqual({ | ||
projectName: 'some-project', | ||
elapsedTimeExcludingLinting: 0, | ||
elapsedTimeIncludingLinting: 2000, | ||
ruleExecutionResultTree: { | ||
children: [ | ||
expect.objectContaining({ | ||
result: { | ||
ruleName: 'rule-2', | ||
ruleDescription: 'Description for rule 2', | ||
passed: true, | ||
}, | ||
children: [ | ||
expect.objectContaining({ | ||
result: { | ||
ruleName: 'rule-1', | ||
ruleDescription: 'Description for rule 1', | ||
passed: false, | ||
failures: [{ message: 'Oops' }], | ||
}, | ||
children: [], | ||
}), | ||
], | ||
}), | ||
], | ||
}, | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import type { MetaMaskRepository } from './establish-metamask-repository'; | ||
import { establishMetaMaskRepository } from './establish-metamask-repository'; | ||
import type { Rule, RootRuleExecutionResultNode } from './execute-rules'; | ||
import { executeRules } from './execute-rules'; | ||
import type { OutputLogger } from './output-logger'; | ||
|
||
/** | ||
* Data collected from linting a project. | ||
*/ | ||
export type ProjectLintResult = { | ||
projectName: string; | ||
elapsedTimeExcludingLinting: number; | ||
elapsedTimeIncludingLinting: number; | ||
ruleExecutionResultTree: RootRuleExecutionResultNode; | ||
}; | ||
|
||
/** | ||
* Executes the given lint rules against a project (either a MetaMask repository | ||
* or a local repository). | ||
* | ||
* @param args - The arguments to this function. | ||
* @param args.projectReference - Either the name of a MetaMask repository, | ||
* such as "utils", or the path to a local Git repository. | ||
* @param args.template - The repository to which the project should be | ||
* compared. | ||
* @param args.rules - The set of checks that should be applied against the | ||
* project. | ||
* @param args.workingDirectoryPath - The directory where this tool was run. | ||
* @param args.cachedRepositoriesDirectoryPath - The directory where MetaMask | ||
* repositories will be (or have been) cloned. | ||
* @param args.outputLogger - Writable streams for output messages. | ||
*/ | ||
export async function lintProject({ | ||
projectReference, | ||
template, | ||
rules, | ||
workingDirectoryPath, | ||
cachedRepositoriesDirectoryPath, | ||
outputLogger, | ||
}: { | ||
projectReference: string; | ||
template: MetaMaskRepository; | ||
rules: readonly Rule[]; | ||
workingDirectoryPath: string; | ||
cachedRepositoriesDirectoryPath: string; | ||
outputLogger: OutputLogger; | ||
}): Promise<ProjectLintResult> { | ||
const startDate = new Date(); | ||
const repository = await establishMetaMaskRepository({ | ||
repositoryReference: projectReference, | ||
workingDirectoryPath, | ||
cachedRepositoriesDirectoryPath, | ||
outputLogger, | ||
}); | ||
const endDateExcludingLinting = new Date(); | ||
const ruleExecutionResultTree = await executeRules({ | ||
rules, | ||
project: repository, | ||
template, | ||
}); | ||
const endDateIncludingLinting = new Date(); | ||
|
||
return { | ||
projectName: repository.shortname, | ||
elapsedTimeExcludingLinting: | ||
endDateExcludingLinting.getTime() - startDate.getTime(), | ||
elapsedTimeIncludingLinting: | ||
endDateIncludingLinting.getTime() - startDate.getTime(), | ||
ruleExecutionResultTree, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { MockWritable } from 'stdio-mock'; | ||
import stripAnsi from 'strip-ansi'; | ||
|
||
import type { ProjectLintResult } from './lint-project'; | ||
import { OutputLogger } from './output-logger'; | ||
import { reportProjectLintResult } from './report-project-lint-result'; | ||
|
||
describe('reportProjectLintResult', () => { | ||
it('outputs the rules executed against a project, in the same hierarchy as they exist, and whether they passed or failed', () => { | ||
const projectLintResult: ProjectLintResult = { | ||
projectName: 'some-project', | ||
elapsedTimeIncludingLinting: 30, | ||
elapsedTimeExcludingLinting: 0, | ||
ruleExecutionResultTree: { | ||
children: [ | ||
{ | ||
result: { | ||
ruleName: 'rule-1', | ||
ruleDescription: 'Description for rule 1', | ||
passed: true, | ||
}, | ||
elapsedTimeExcludingChildren: 0, | ||
elapsedTimeIncludingChildren: 0, | ||
children: [ | ||
{ | ||
result: { | ||
ruleName: 'rule-2', | ||
ruleDescription: 'Description for rule 2', | ||
passed: false, | ||
failures: [ | ||
{ message: 'Failure 1' }, | ||
{ message: 'Failure 2' }, | ||
], | ||
}, | ||
elapsedTimeExcludingChildren: 0, | ||
elapsedTimeIncludingChildren: 0, | ||
children: [], | ||
}, | ||
], | ||
}, | ||
{ | ||
result: { | ||
ruleName: 'rule-3', | ||
ruleDescription: 'Description for rule 3', | ||
passed: true, | ||
}, | ||
elapsedTimeExcludingChildren: 0, | ||
elapsedTimeIncludingChildren: 0, | ||
children: [], | ||
}, | ||
], | ||
}, | ||
}; | ||
const stdout = new MockWritable(); | ||
const stderr = new MockWritable(); | ||
const outputLogger = new OutputLogger({ stdout, stderr }); | ||
|
||
reportProjectLintResult({ | ||
projectLintResult, | ||
outputLogger, | ||
}); | ||
|
||
const output = stdout.data().map(stripAnsi).join(''); | ||
|
||
expect(output).toBe( | ||
` | ||
some-project | ||
------------ | ||
Linted project in 30 ms. | ||
- Description for rule 1 ✅ | ||
- Description for rule 2 ❌ | ||
- Failure 1 | ||
- Failure 2 | ||
- Description for rule 3 ✅ | ||
`, | ||
); | ||
}); | ||
}); |
Oops, something went wrong.