mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 17:33:23 +01:00
Prevent new JS files in shared folder (#17737)
* Prevent new JS files in shared folder * migrate to typescript * fix types * cleanup
This commit is contained in:
parent
3e520214c9
commit
632ae0b7c3
2
.github/workflows/fitness-functions.yml
vendored
2
.github/workflows/fitness-functions.yml
vendored
@ -24,5 +24,5 @@ jobs:
|
||||
run: |
|
||||
# git fetch origin $HEAD_REF
|
||||
# git fetch origin $BASE_REF
|
||||
# git diff origin/$BASE_REF origin/$HEAD_REF -- . ':(exclude)development/fitness-functions/*' > diff
|
||||
# git diff origin/$BASE_REF origin/$HEAD_REF -- . > diff
|
||||
# npm run fitness-functions -- "ci" "./diff"
|
||||
|
@ -1,44 +0,0 @@
|
||||
const {
|
||||
EXCLUDE_E2E_TESTS_REGEX,
|
||||
filterDiffAdditions,
|
||||
filterDiffByFilePath,
|
||||
hasNumberOfCodeBlocksIncreased,
|
||||
} = require('./shared');
|
||||
|
||||
function checkMochaSyntax(diff) {
|
||||
const ruleHeading = 'favor-jest-instead-of-mocha';
|
||||
const codeBlocks = [
|
||||
"import { strict as assert } from 'assert';",
|
||||
'assert.deepEqual',
|
||||
'assert.equal',
|
||||
'assert.rejects',
|
||||
'assert.strictEqual',
|
||||
'sinon.',
|
||||
];
|
||||
|
||||
console.log(`\nChecking ${ruleHeading}...`);
|
||||
|
||||
const diffByFilePath = filterDiffByFilePath(diff, EXCLUDE_E2E_TESTS_REGEX);
|
||||
const diffAdditions = filterDiffAdditions(diffByFilePath);
|
||||
const hashmap = hasNumberOfCodeBlocksIncreased(diffAdditions, codeBlocks);
|
||||
|
||||
Object.keys(hashmap).forEach((key) => {
|
||||
if (hashmap[key]) {
|
||||
console.error(`Number of occurences of "${key}" have increased.`);
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.values(hashmap).includes(true)) {
|
||||
console.error(
|
||||
`...changes have not been accepted by the fitness function.\nFor more info, see: https://github.com/MetaMask/metamask-extension/blob/develop/docs/testing.md#${ruleHeading}`,
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(
|
||||
`...number of occurences has not increased for any code block.`,
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { checkMochaSyntax };
|
105
development/fitness-functions/common/constants.test.ts
Normal file
105
development/fitness-functions/common/constants.test.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { EXCLUDE_E2E_TESTS_REGEX, SHARED_FOLDER_JS_REGEX } from './constants';
|
||||
|
||||
describe('Regular Expressions used in Fitness Functions', (): void => {
|
||||
describe(`EXCLUDE_E2E_TESTS_REGEX "${EXCLUDE_E2E_TESTS_REGEX}"`, (): void => {
|
||||
const PATHS_IT_SHOULD_MATCH = [
|
||||
'file.js',
|
||||
'path/file.js',
|
||||
'much/longer/path/file.js',
|
||||
'file.ts',
|
||||
'path/file.ts',
|
||||
'much/longer/path/file.ts',
|
||||
'file.jsx',
|
||||
'path/file.jsx',
|
||||
'much/longer/path/file.jsx',
|
||||
];
|
||||
|
||||
const PATHS_IT_SHOULD_NOT_MATCH = [
|
||||
// any without JS, TS, JSX or TSX extension
|
||||
'file',
|
||||
'file.extension',
|
||||
'path/file.extension',
|
||||
'much/longer/path/file.extension',
|
||||
// any in the test/e2e directory
|
||||
'test/e2e/file',
|
||||
'test/e2e/file.extension',
|
||||
'test/e2e/path/file.extension',
|
||||
'test/e2e/much/longer/path/file.extension',
|
||||
'test/e2e/file.js',
|
||||
'test/e2e/path/file.ts',
|
||||
'test/e2e/much/longer/path/file.jsx',
|
||||
'test/e2e/much/longer/path/file.tsx',
|
||||
// any in the development/fitness-functions directory
|
||||
'development/fitness-functions/file',
|
||||
'development/fitness-functions/file.extension',
|
||||
'development/fitness-functions/path/file.extension',
|
||||
'development/fitness-functions/much/longer/path/file.extension',
|
||||
'development/fitness-functions/file.js',
|
||||
'development/fitness-functions/path/file.ts',
|
||||
'development/fitness-functions/much/longer/path/file.jsx',
|
||||
'development/fitness-functions/much/longer/path/file.tsx',
|
||||
];
|
||||
|
||||
describe('included paths', (): void => {
|
||||
PATHS_IT_SHOULD_MATCH.forEach((path: string): void => {
|
||||
it(`should match "${path}"`, (): void => {
|
||||
const result = new RegExp(EXCLUDE_E2E_TESTS_REGEX, 'u').test(path);
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('excluded paths', (): void => {
|
||||
PATHS_IT_SHOULD_NOT_MATCH.forEach((path: string): void => {
|
||||
it(`should not match "${path}"`, (): void => {
|
||||
const result = new RegExp(EXCLUDE_E2E_TESTS_REGEX, 'u').test(path);
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`SHARED_FOLDER_JS_REGEX "${SHARED_FOLDER_JS_REGEX}"`, (): void => {
|
||||
const PATHS_IT_SHOULD_MATCH = [
|
||||
'shared/file.js',
|
||||
'shared/path/file.js',
|
||||
'shared/much/longer/path/file.js',
|
||||
'shared/file.jsx',
|
||||
'shared/path/file.jsx',
|
||||
'shared/much/longer/path/file.jsx',
|
||||
];
|
||||
|
||||
const PATHS_IT_SHOULD_NOT_MATCH = [
|
||||
// any without JS or JSX extension
|
||||
'file',
|
||||
'file.extension',
|
||||
'path/file.extension',
|
||||
'much/longer/path/file.extension',
|
||||
'file.ts',
|
||||
'path/file.ts',
|
||||
'much/longer/path/file.tsx',
|
||||
];
|
||||
|
||||
describe('included paths', (): void => {
|
||||
PATHS_IT_SHOULD_MATCH.forEach((path: string): void => {
|
||||
it(`should match "${path}"`, (): void => {
|
||||
const result = new RegExp(SHARED_FOLDER_JS_REGEX, 'u').test(path);
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('excluded paths', (): void => {
|
||||
PATHS_IT_SHOULD_NOT_MATCH.forEach((path: string): void => {
|
||||
it(`should not match "${path}"`, (): void => {
|
||||
const result = new RegExp(SHARED_FOLDER_JS_REGEX, 'u').test(path);
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
15
development/fitness-functions/common/constants.ts
Normal file
15
development/fitness-functions/common/constants.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// include JS, TS, JSX, TSX files only excluding files in the e2e tests and
|
||||
// fitness functions directories
|
||||
const EXCLUDE_E2E_TESTS_REGEX =
|
||||
'^(?!test/e2e)(?!development/fitness).*.(js|ts|jsx|tsx)$';
|
||||
|
||||
// include JS and JSX files in the shared directory only
|
||||
const SHARED_FOLDER_JS_REGEX = '^(shared).*.(js|jsx)$';
|
||||
|
||||
enum AUTOMATION_TYPE {
|
||||
CI = 'ci',
|
||||
PRE_COMMIT_HOOK = 'pre-commit-hook',
|
||||
PRE_PUSH_HOOK = 'pre-push-hook',
|
||||
}
|
||||
|
||||
export { EXCLUDE_E2E_TESTS_REGEX, SHARED_FOLDER_JS_REGEX, AUTOMATION_TYPE };
|
54
development/fitness-functions/common/get-diff.ts
Normal file
54
development/fitness-functions/common/get-diff.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import { AUTOMATION_TYPE } from './constants';
|
||||
|
||||
function getDiffByAutomationType(
|
||||
automationType: AUTOMATION_TYPE,
|
||||
): string | undefined {
|
||||
if (!Object.values(AUTOMATION_TYPE).includes(automationType)) {
|
||||
console.error('Invalid automation type.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let diff;
|
||||
if (automationType === AUTOMATION_TYPE.CI) {
|
||||
const optionalArguments = process.argv.slice(3);
|
||||
if (optionalArguments.length !== 1) {
|
||||
console.error('Invalid number of arguments.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
diff = getCIDiff(optionalArguments[0]);
|
||||
} else if (automationType === AUTOMATION_TYPE.PRE_COMMIT_HOOK) {
|
||||
diff = getPreCommitHookDiff();
|
||||
} else if (automationType === AUTOMATION_TYPE.PRE_PUSH_HOOK) {
|
||||
diff = getPrePushHookDiff();
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
function getCIDiff(path: string): string {
|
||||
return fs.readFileSync(path, {
|
||||
encoding: 'utf8',
|
||||
flag: 'r',
|
||||
});
|
||||
}
|
||||
|
||||
function getPreCommitHookDiff(): string {
|
||||
return execSync(`git diff --cached HEAD`).toString().trim();
|
||||
}
|
||||
|
||||
function getPrePushHookDiff(): string {
|
||||
const currentBranch = execSync(`git rev-parse --abbrev-ref HEAD`)
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
return execSync(
|
||||
`git diff ${currentBranch} origin/${currentBranch} -- . ':(exclude)development/fitness-functions/'`,
|
||||
)
|
||||
.toString()
|
||||
.trim();
|
||||
}
|
||||
|
||||
export { getDiffByAutomationType };
|
@ -1,46 +1,48 @@
|
||||
const {
|
||||
EXCLUDE_E2E_TESTS_REGEX,
|
||||
filterDiffAdditions,
|
||||
import {
|
||||
filterDiffLineAdditions,
|
||||
hasNumberOfCodeBlocksIncreased,
|
||||
filterDiffByFilePath,
|
||||
} = require('./shared');
|
||||
filterDiffFileCreations,
|
||||
} from './shared';
|
||||
import { generateCreateFileDiff, generateModifyFilesDiff } from './test-data';
|
||||
|
||||
const generateCreateFileDiff = (filePath, content) => `
|
||||
diff --git a/${filePath} b/${filePath}
|
||||
new file mode 100644
|
||||
index 000000000..30d74d258
|
||||
--- /dev/null
|
||||
+++ b/${filePath}
|
||||
@@ -0,0 +1 @@
|
||||
+${content}
|
||||
`;
|
||||
|
||||
const generateModifyFilesDiff = (filePath, addition, removal) => `
|
||||
diff --git a/${filePath} b/${filePath}
|
||||
index 57d5de75c..808d8ba37 100644
|
||||
--- a/${filePath}
|
||||
+++ b/${filePath}
|
||||
@@ -1,3 +1,8 @@
|
||||
+${addition}
|
||||
@@ -34,33 +39,4 @@
|
||||
-${removal}
|
||||
`;
|
||||
|
||||
describe('filterDiffAdditions()', () => {
|
||||
it('should return code additions in the diff', () => {
|
||||
describe('filterDiffLineAdditions()', (): void => {
|
||||
it('should return code additions in the diff', (): void => {
|
||||
const testFilePath = 'new-file.js';
|
||||
const testAddition = 'foo';
|
||||
const testFileDiff = generateCreateFileDiff(testFilePath, testAddition);
|
||||
|
||||
const actualResult = filterDiffAdditions(testFileDiff);
|
||||
const actualResult = filterDiffLineAdditions(testFileDiff);
|
||||
const expectedResult = `+${testAddition}`;
|
||||
|
||||
expect(actualResult).toStrictEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasNumberOfCodeBlocksIncreased()', () => {
|
||||
it('should show which code blocks have increased', () => {
|
||||
describe('filterDiffFileCreations()', (): void => {
|
||||
it('should return code additions in the diff', (): void => {
|
||||
const testFileDiff = [
|
||||
generateModifyFilesDiff('new-file.ts', 'foo', 'bar'),
|
||||
generateCreateFileDiff('old-file.js', 'ping'),
|
||||
generateModifyFilesDiff('old-file.jsx', 'yin', 'yang'),
|
||||
].join('');
|
||||
|
||||
const actualResult = filterDiffFileCreations(testFileDiff);
|
||||
|
||||
expect(actualResult).toMatchInlineSnapshot(`
|
||||
"diff --git a/old-file.js b/old-file.js
|
||||
new file mode 100644
|
||||
index 000000000..30d74d258
|
||||
--- /dev/null
|
||||
+++ b/old-file.js
|
||||
@@ -0,0 +1 @@
|
||||
+ping"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasNumberOfCodeBlocksIncreased()', (): void => {
|
||||
it('should show which code blocks have increased', (): void => {
|
||||
const testDiffFragment = `
|
||||
+foo
|
||||
+bar
|
||||
@ -57,14 +59,14 @@ describe('hasNumberOfCodeBlocksIncreased()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterDiffByFilePath()', () => {
|
||||
describe('filterDiffByFilePath()', (): void => {
|
||||
const testFileDiff = [
|
||||
generateModifyFilesDiff('new-file.ts', 'foo', 'bar'),
|
||||
generateModifyFilesDiff('old-file.js', 'ping', 'pong'),
|
||||
generateModifyFilesDiff('old-file.jsx', 'yin', 'yang'),
|
||||
].join('');
|
||||
|
||||
it('should return the right diff for a generic matcher', () => {
|
||||
it('should return the right diff for a generic matcher', (): void => {
|
||||
const actualResult = filterDiffByFilePath(
|
||||
testFileDiff,
|
||||
'.*/.*.(js|ts)$|.*.(js|ts)$',
|
||||
@ -90,7 +92,7 @@ describe('filterDiffByFilePath()', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return the right diff for a specific file in any dir matcher', () => {
|
||||
it('should return the right diff for a specific file in any dir matcher', (): void => {
|
||||
const actualResult = filterDiffByFilePath(testFileDiff, '.*old-file.js$');
|
||||
|
||||
expect(actualResult).toMatchInlineSnapshot(`
|
||||
@ -105,7 +107,7 @@ describe('filterDiffByFilePath()', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return the right diff for a multiple file extension (OR) matcher', () => {
|
||||
it('should return the right diff for a multiple file extension (OR) matcher', (): void => {
|
||||
const actualResult = filterDiffByFilePath(
|
||||
testFileDiff,
|
||||
'^(./)*old-file.(js|ts|jsx)$',
|
||||
@ -131,7 +133,7 @@ describe('filterDiffByFilePath()', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return the right diff for a file name negation matcher', () => {
|
||||
it('should return the right diff for a file name negation matcher', (): void => {
|
||||
const actualResult = filterDiffByFilePath(
|
||||
testFileDiff,
|
||||
'^(?!.*old-file.js$).*.[a-zA-Z]+$',
|
||||
@ -157,51 +159,3 @@ describe('filterDiffByFilePath()', () => {
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`EXCLUDE_E2E_TESTS_REGEX "${EXCLUDE_E2E_TESTS_REGEX}"`, () => {
|
||||
const PATHS_IT_SHOULD_MATCH = [
|
||||
'file.js',
|
||||
'path/file.js',
|
||||
'much/longer/path/file.js',
|
||||
'file.ts',
|
||||
'path/file.ts',
|
||||
'much/longer/path/file.ts',
|
||||
'file.jsx',
|
||||
'path/file.jsx',
|
||||
'much/longer/path/file.jsx',
|
||||
];
|
||||
|
||||
const PATHS_IT_SHOULD_NOT_MATCH = [
|
||||
'test/e2e/file',
|
||||
'test/e2e/file.extension',
|
||||
'test/e2e/path/file.extension',
|
||||
'test/e2e/much/longer/path/file.extension',
|
||||
'test/e2e/file.js',
|
||||
'test/e2e/path/file.ts',
|
||||
'test/e2e/much/longer/path/file.jsx',
|
||||
'file',
|
||||
'file.extension',
|
||||
'path/file.extension',
|
||||
'much/longer/path/file.extension',
|
||||
];
|
||||
|
||||
describe('included paths', () => {
|
||||
PATHS_IT_SHOULD_MATCH.forEach((path) => {
|
||||
it(`should match "${path}"`, () => {
|
||||
const result = new RegExp(EXCLUDE_E2E_TESTS_REGEX, 'u').test(path);
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('excluded paths', () => {
|
||||
PATHS_IT_SHOULD_NOT_MATCH.forEach((path) => {
|
||||
it(`should not match "${path}"`, () => {
|
||||
const result = new RegExp(EXCLUDE_E2E_TESTS_REGEX, 'u').test(path);
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,6 +1,4 @@
|
||||
const EXCLUDE_E2E_TESTS_REGEX = '^(?!test/e2e/).*.(js|ts|jsx)$';
|
||||
|
||||
function filterDiffByFilePath(diff, regex) {
|
||||
function filterDiffByFilePath(diff: string, regex: string): string {
|
||||
// split by `diff --git` and remove the first element which is empty
|
||||
const diffBlocks = diff.split(`diff --git`).slice(1);
|
||||
|
||||
@ -34,7 +32,7 @@ function filterDiffByFilePath(diff, regex) {
|
||||
return filteredDiff;
|
||||
}
|
||||
|
||||
function filterDiffAdditions(diff) {
|
||||
function filterDiffLineAdditions(diff: string): string {
|
||||
const diffLines = diff.split('\n');
|
||||
|
||||
const diffAdditionLines = diffLines.filter((line) => {
|
||||
@ -46,10 +44,36 @@ function filterDiffAdditions(diff) {
|
||||
return diffAdditionLines.join('/n');
|
||||
}
|
||||
|
||||
function hasNumberOfCodeBlocksIncreased(diffFragment, codeBlocks) {
|
||||
function filterDiffFileCreations(diff: string): string {
|
||||
// split by `diff --git` and remove the first element which is empty
|
||||
const diffBlocks = diff.split(`diff --git`).slice(1);
|
||||
|
||||
const filteredDiff = diffBlocks
|
||||
.map((block) => block.trim())
|
||||
.filter((block) => {
|
||||
const isFileCreationLine =
|
||||
block
|
||||
// get the second line of the block which has the file mode
|
||||
.split('\n')[1]
|
||||
.trim()
|
||||
.substring(0, 13) === 'new file mode';
|
||||
|
||||
return isFileCreationLine;
|
||||
})
|
||||
// prepend `git --diff` to each block
|
||||
.map((block) => `diff --git ${block}`)
|
||||
.join('\n');
|
||||
|
||||
return filteredDiff;
|
||||
}
|
||||
|
||||
function hasNumberOfCodeBlocksIncreased(
|
||||
diffFragment: string,
|
||||
codeBlocks: string[],
|
||||
): { [codeBlock: string]: boolean } {
|
||||
const diffLines = diffFragment.split('\n');
|
||||
|
||||
const codeBlockFound = {};
|
||||
const codeBlockFound: { [codeBlock: string]: boolean } = {};
|
||||
|
||||
for (const codeBlock of codeBlocks) {
|
||||
codeBlockFound[codeBlock] = false;
|
||||
@ -65,9 +89,9 @@ function hasNumberOfCodeBlocksIncreased(diffFragment, codeBlocks) {
|
||||
return codeBlockFound;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
EXCLUDE_E2E_TESTS_REGEX,
|
||||
export {
|
||||
filterDiffByFilePath,
|
||||
filterDiffAdditions,
|
||||
filterDiffLineAdditions,
|
||||
filterDiffFileCreations,
|
||||
hasNumberOfCodeBlocksIncreased,
|
||||
};
|
40
development/fitness-functions/common/test-data.ts
Normal file
40
development/fitness-functions/common/test-data.ts
Normal file
@ -0,0 +1,40 @@
|
||||
const generateCreateFileDiff = (
|
||||
filePath = 'file.txt',
|
||||
content = 'Lorem ipsum',
|
||||
): string => `
|
||||
diff --git a/${filePath} b/${filePath}
|
||||
new file mode 100644
|
||||
index 000000000..30d74d258
|
||||
--- /dev/null
|
||||
+++ b/${filePath}
|
||||
@@ -0,0 +1 @@
|
||||
+${content}
|
||||
`;
|
||||
|
||||
const generateModifyFilesDiff = (
|
||||
filePath = 'file.txt',
|
||||
addition = 'Lorem ipsum',
|
||||
removal = '',
|
||||
): string => {
|
||||
const additionBlock = addition
|
||||
? `
|
||||
@@ -1,3 +1,8 @@
|
||||
+${addition}`.trim()
|
||||
: '';
|
||||
|
||||
const removalBlock = removal
|
||||
? `
|
||||
@@ -34,33 +39,4 @@
|
||||
-${removal}`.trim()
|
||||
: '';
|
||||
|
||||
return `
|
||||
diff --git a/${filePath} b/${filePath}
|
||||
index 57d5de75c..808d8ba37 100644
|
||||
--- a/${filePath}
|
||||
+++ b/${filePath}
|
||||
${additionBlock}
|
||||
${removalBlock}`;
|
||||
};
|
||||
|
||||
export { generateCreateFileDiff, generateModifyFilesDiff };
|
@ -1,53 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
const { checkMochaSyntax } = require('./check-mocha-syntax');
|
||||
|
||||
const AUTOMATION_TYPE = Object.freeze({
|
||||
CI: 'ci',
|
||||
PRE_COMMIT_HOOK: 'pre-commit-hook',
|
||||
PRE_PUSH_HOOK: 'pre-push-hook',
|
||||
});
|
||||
|
||||
const automationType = process.argv[2];
|
||||
|
||||
if (automationType === AUTOMATION_TYPE.CI) {
|
||||
const optionalArguments = process.argv.slice(3);
|
||||
if (optionalArguments.length !== 1) {
|
||||
console.error('Invalid number of arguments.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const diff = fs.readFileSync(optionalArguments[0], {
|
||||
encoding: 'utf8',
|
||||
flag: 'r',
|
||||
});
|
||||
|
||||
checkMochaSyntax(diff);
|
||||
} else if (automationType === AUTOMATION_TYPE.PRE_COMMIT_HOOK) {
|
||||
const diff = getPreCommitHookDiff();
|
||||
|
||||
checkMochaSyntax(diff);
|
||||
} else if (automationType === AUTOMATION_TYPE.PRE_PUSH_HOOK) {
|
||||
const diff = getPrePushHookDiff();
|
||||
|
||||
checkMochaSyntax(diff);
|
||||
} else {
|
||||
console.error('Invalid automation type.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function getPreCommitHookDiff() {
|
||||
return execSync(`git diff --cached HEAD`).toString().trim();
|
||||
}
|
||||
|
||||
function getPrePushHookDiff() {
|
||||
const currentBranch = execSync(`git rev-parse --abbrev-ref HEAD`)
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
return execSync(
|
||||
`git diff ${currentBranch} origin/${currentBranch} -- . ':(exclude)development/fitness-functions/'`,
|
||||
)
|
||||
.toString()
|
||||
.trim();
|
||||
}
|
11
development/fitness-functions/index.ts
Normal file
11
development/fitness-functions/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { AUTOMATION_TYPE } from './common/constants';
|
||||
import { getDiffByAutomationType } from './common/get-diff';
|
||||
import { IRule, RULES, runFitnessFunctionRule } from './rules';
|
||||
|
||||
const automationType: AUTOMATION_TYPE = process.argv[2] as AUTOMATION_TYPE;
|
||||
|
||||
const diff = getDiffByAutomationType(automationType);
|
||||
|
||||
if (typeof diff === 'string') {
|
||||
RULES.forEach((rule: IRule): void => runFitnessFunctionRule(rule, diff));
|
||||
}
|
42
development/fitness-functions/rules/index.ts
Normal file
42
development/fitness-functions/rules/index.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { preventSinonAssertSyntax } from './sinon-assert-syntax';
|
||||
import { preventJavaScriptFileAdditions } from './javascript-additions';
|
||||
|
||||
const RULES: IRule[] = [
|
||||
{
|
||||
name: "Don't use `sinon` or `assert` in unit tests",
|
||||
fn: preventSinonAssertSyntax,
|
||||
docURL:
|
||||
'https://github.com/MetaMask/metamask-extension/blob/develop/docs/testing.md#favor-jest-instead-of-mocha',
|
||||
},
|
||||
{
|
||||
name: "Don't add JS or JSX files to the `shared` directory",
|
||||
fn: preventJavaScriptFileAdditions,
|
||||
},
|
||||
];
|
||||
|
||||
interface IRule {
|
||||
name: string;
|
||||
fn: (diff: string) => boolean;
|
||||
docURL?: string;
|
||||
}
|
||||
|
||||
function runFitnessFunctionRule(rule: IRule, diff: string): void {
|
||||
const { name, fn, docURL } = rule;
|
||||
console.log(`Checking rule "${name}"...`);
|
||||
|
||||
const hasRulePassed: boolean = fn(diff) as boolean;
|
||||
if (hasRulePassed === true) {
|
||||
console.log(`...OK`);
|
||||
} else {
|
||||
console.log(`...FAILED. Changes not accepted by the fitness function.`);
|
||||
|
||||
if (docURL) {
|
||||
console.log(`For more info: ${docURL}.`);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export { RULES, runFitnessFunctionRule };
|
||||
export type { IRule };
|
@ -0,0 +1,51 @@
|
||||
import {
|
||||
generateModifyFilesDiff,
|
||||
generateCreateFileDiff,
|
||||
} from '../common/test-data';
|
||||
import { preventJavaScriptFileAdditions } from './javascript-additions';
|
||||
|
||||
describe('preventJavaScriptFileAdditions()', (): void => {
|
||||
it('should pass when receiving an empty diff', (): void => {
|
||||
const testDiff = '';
|
||||
|
||||
const hasRulePassed = preventJavaScriptFileAdditions(testDiff);
|
||||
|
||||
expect(hasRulePassed).toBe(true);
|
||||
});
|
||||
|
||||
it('should pass when receiving a diff with a new TS file on the shared folder', (): void => {
|
||||
const testDiff = [
|
||||
generateModifyFilesDiff('new-file.ts', 'foo', 'bar'),
|
||||
generateModifyFilesDiff('old-file.js', undefined, 'pong'),
|
||||
generateCreateFileDiff('shared/test.ts', 'yada yada yada yada'),
|
||||
].join('');
|
||||
|
||||
const hasRulePassed = preventJavaScriptFileAdditions(testDiff);
|
||||
|
||||
expect(hasRulePassed).toBe(true);
|
||||
});
|
||||
|
||||
it('should not pass when receiving a diff with a new JS file on the shared folder', (): void => {
|
||||
const testDiff = [
|
||||
generateModifyFilesDiff('new-file.ts', 'foo', 'bar'),
|
||||
generateModifyFilesDiff('old-file.js', undefined, 'pong'),
|
||||
generateCreateFileDiff('shared/test.js', 'yada yada yada yada'),
|
||||
].join('');
|
||||
|
||||
const hasRulePassed = preventJavaScriptFileAdditions(testDiff);
|
||||
|
||||
expect(hasRulePassed).toBe(false);
|
||||
});
|
||||
|
||||
it('should not pass when receiving a diff with a new JSX file on the shared folder', (): void => {
|
||||
const testDiff = [
|
||||
generateModifyFilesDiff('new-file.ts', 'foo', 'bar'),
|
||||
generateModifyFilesDiff('old-file.js', undefined, 'pong'),
|
||||
generateCreateFileDiff('shared/test.jsx', 'yada yada yada yada'),
|
||||
].join('');
|
||||
|
||||
const hasRulePassed = preventJavaScriptFileAdditions(testDiff);
|
||||
|
||||
expect(hasRulePassed).toBe(false);
|
||||
});
|
||||
});
|
18
development/fitness-functions/rules/javascript-additions.ts
Normal file
18
development/fitness-functions/rules/javascript-additions.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { SHARED_FOLDER_JS_REGEX } from '../common/constants';
|
||||
import {
|
||||
filterDiffByFilePath,
|
||||
filterDiffFileCreations,
|
||||
} from '../common/shared';
|
||||
|
||||
function preventJavaScriptFileAdditions(diff: string): boolean {
|
||||
const sharedFolderDiff = filterDiffByFilePath(diff, SHARED_FOLDER_JS_REGEX);
|
||||
const sharedFolderCreationDiff = filterDiffFileCreations(sharedFolderDiff);
|
||||
|
||||
const hasCreatedAtLeastOneJSFileInShared = sharedFolderCreationDiff !== '';
|
||||
if (hasCreatedAtLeastOneJSFileInShared) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export { preventJavaScriptFileAdditions };
|
@ -0,0 +1,29 @@
|
||||
import { generateModifyFilesDiff } from '../common/test-data';
|
||||
import { preventSinonAssertSyntax } from './sinon-assert-syntax';
|
||||
|
||||
describe('preventSinonAssertSyntax()', (): void => {
|
||||
it('should pass when receiving an empty diff', (): void => {
|
||||
const testDiff = '';
|
||||
|
||||
const hasRulePassed = preventSinonAssertSyntax(testDiff);
|
||||
|
||||
expect(hasRulePassed).toBe(true);
|
||||
});
|
||||
|
||||
it('should not pass when receiving a diff with one of the blocked expressions', (): void => {
|
||||
const infringingExpression = 'assert.equal';
|
||||
const testDiff = [
|
||||
generateModifyFilesDiff('new-file.ts', 'foo', 'bar'),
|
||||
generateModifyFilesDiff('old-file.js', undefined, 'pong'),
|
||||
generateModifyFilesDiff(
|
||||
'test.js',
|
||||
`yada yada ${infringingExpression} yada yada`,
|
||||
undefined,
|
||||
),
|
||||
].join('');
|
||||
|
||||
const hasRulePassed = preventSinonAssertSyntax(testDiff);
|
||||
|
||||
expect(hasRulePassed).toBe(false);
|
||||
});
|
||||
});
|
30
development/fitness-functions/rules/sinon-assert-syntax.ts
Normal file
30
development/fitness-functions/rules/sinon-assert-syntax.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { EXCLUDE_E2E_TESTS_REGEX } from '../common/constants';
|
||||
import {
|
||||
filterDiffLineAdditions,
|
||||
filterDiffByFilePath,
|
||||
hasNumberOfCodeBlocksIncreased,
|
||||
} from '../common/shared';
|
||||
|
||||
const codeBlocks = [
|
||||
"import { strict as assert } from 'assert';",
|
||||
'assert.deepEqual',
|
||||
'assert.equal',
|
||||
'assert.rejects',
|
||||
'assert.strictEqual',
|
||||
'sinon.',
|
||||
];
|
||||
|
||||
function preventSinonAssertSyntax(diff: string): boolean {
|
||||
const diffByFilePath = filterDiffByFilePath(diff, EXCLUDE_E2E_TESTS_REGEX);
|
||||
const diffAdditions = filterDiffLineAdditions(diffByFilePath);
|
||||
const hashmap = hasNumberOfCodeBlocksIncreased(diffAdditions, codeBlocks);
|
||||
|
||||
const haveOccurencesOfAtLeastOneCodeBlockIncreased =
|
||||
Object.values(hashmap).includes(true);
|
||||
if (haveOccurencesOfAtLeastOneCodeBlockIncreased) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export { preventSinonAssertSyntax };
|
@ -91,7 +91,7 @@
|
||||
"test-storybook": "test-storybook -c .storybook",
|
||||
"test-storybook:ci": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"yarn storybook:build && npx http-server storybook-build --port 6006 \" \"wait-on tcp:6006 && yarn test-storybook --maxWorkers=2\"",
|
||||
"githooks:install": "husky install",
|
||||
"fitness-functions": "node development/fitness-functions/index.js",
|
||||
"fitness-functions": "ts-node development/fitness-functions/index.ts",
|
||||
"generate-beta-commit": "node ./development/generate-beta-commit.js"
|
||||
},
|
||||
"resolutions": {
|
||||
|
Loading…
Reference in New Issue
Block a user