mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +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: |
|
run: |
|
||||||
# git fetch origin $HEAD_REF
|
# git fetch origin $HEAD_REF
|
||||||
# git fetch origin $BASE_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"
|
# 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 {
|
import {
|
||||||
EXCLUDE_E2E_TESTS_REGEX,
|
filterDiffLineAdditions,
|
||||||
filterDiffAdditions,
|
|
||||||
hasNumberOfCodeBlocksIncreased,
|
hasNumberOfCodeBlocksIncreased,
|
||||||
filterDiffByFilePath,
|
filterDiffByFilePath,
|
||||||
} = require('./shared');
|
filterDiffFileCreations,
|
||||||
|
} from './shared';
|
||||||
|
import { generateCreateFileDiff, generateModifyFilesDiff } from './test-data';
|
||||||
|
|
||||||
const generateCreateFileDiff = (filePath, content) => `
|
describe('filterDiffLineAdditions()', (): void => {
|
||||||
diff --git a/${filePath} b/${filePath}
|
it('should return code additions in the diff', (): void => {
|
||||||
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', () => {
|
|
||||||
const testFilePath = 'new-file.js';
|
const testFilePath = 'new-file.js';
|
||||||
const testAddition = 'foo';
|
const testAddition = 'foo';
|
||||||
const testFileDiff = generateCreateFileDiff(testFilePath, testAddition);
|
const testFileDiff = generateCreateFileDiff(testFilePath, testAddition);
|
||||||
|
|
||||||
const actualResult = filterDiffAdditions(testFileDiff);
|
const actualResult = filterDiffLineAdditions(testFileDiff);
|
||||||
const expectedResult = `+${testAddition}`;
|
const expectedResult = `+${testAddition}`;
|
||||||
|
|
||||||
expect(actualResult).toStrictEqual(expectedResult);
|
expect(actualResult).toStrictEqual(expectedResult);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hasNumberOfCodeBlocksIncreased()', () => {
|
describe('filterDiffFileCreations()', (): void => {
|
||||||
it('should show which code blocks have increased', () => {
|
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 = `
|
const testDiffFragment = `
|
||||||
+foo
|
+foo
|
||||||
+bar
|
+bar
|
||||||
@ -57,14 +59,14 @@ describe('hasNumberOfCodeBlocksIncreased()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('filterDiffByFilePath()', () => {
|
describe('filterDiffByFilePath()', (): void => {
|
||||||
const testFileDiff = [
|
const testFileDiff = [
|
||||||
generateModifyFilesDiff('new-file.ts', 'foo', 'bar'),
|
generateModifyFilesDiff('new-file.ts', 'foo', 'bar'),
|
||||||
generateModifyFilesDiff('old-file.js', 'ping', 'pong'),
|
generateModifyFilesDiff('old-file.js', 'ping', 'pong'),
|
||||||
generateModifyFilesDiff('old-file.jsx', 'yin', 'yang'),
|
generateModifyFilesDiff('old-file.jsx', 'yin', 'yang'),
|
||||||
].join('');
|
].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(
|
const actualResult = filterDiffByFilePath(
|
||||||
testFileDiff,
|
testFileDiff,
|
||||||
'.*/.*.(js|ts)$|.*.(js|ts)$',
|
'.*/.*.(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$');
|
const actualResult = filterDiffByFilePath(testFileDiff, '.*old-file.js$');
|
||||||
|
|
||||||
expect(actualResult).toMatchInlineSnapshot(`
|
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(
|
const actualResult = filterDiffByFilePath(
|
||||||
testFileDiff,
|
testFileDiff,
|
||||||
'^(./)*old-file.(js|ts|jsx)$',
|
'^(./)*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(
|
const actualResult = filterDiffByFilePath(
|
||||||
testFileDiff,
|
testFileDiff,
|
||||||
'^(?!.*old-file.js$).*.[a-zA-Z]+$',
|
'^(?!.*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: string, regex: string): string {
|
||||||
|
|
||||||
function filterDiffByFilePath(diff, regex) {
|
|
||||||
// split by `diff --git` and remove the first element which is empty
|
// split by `diff --git` and remove the first element which is empty
|
||||||
const diffBlocks = diff.split(`diff --git`).slice(1);
|
const diffBlocks = diff.split(`diff --git`).slice(1);
|
||||||
|
|
||||||
@ -34,7 +32,7 @@ function filterDiffByFilePath(diff, regex) {
|
|||||||
return filteredDiff;
|
return filteredDiff;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterDiffAdditions(diff) {
|
function filterDiffLineAdditions(diff: string): string {
|
||||||
const diffLines = diff.split('\n');
|
const diffLines = diff.split('\n');
|
||||||
|
|
||||||
const diffAdditionLines = diffLines.filter((line) => {
|
const diffAdditionLines = diffLines.filter((line) => {
|
||||||
@ -46,10 +44,36 @@ function filterDiffAdditions(diff) {
|
|||||||
return diffAdditionLines.join('/n');
|
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 diffLines = diffFragment.split('\n');
|
||||||
|
|
||||||
const codeBlockFound = {};
|
const codeBlockFound: { [codeBlock: string]: boolean } = {};
|
||||||
|
|
||||||
for (const codeBlock of codeBlocks) {
|
for (const codeBlock of codeBlocks) {
|
||||||
codeBlockFound[codeBlock] = false;
|
codeBlockFound[codeBlock] = false;
|
||||||
@ -65,9 +89,9 @@ function hasNumberOfCodeBlocksIncreased(diffFragment, codeBlocks) {
|
|||||||
return codeBlockFound;
|
return codeBlockFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export {
|
||||||
EXCLUDE_E2E_TESTS_REGEX,
|
|
||||||
filterDiffByFilePath,
|
filterDiffByFilePath,
|
||||||
filterDiffAdditions,
|
filterDiffLineAdditions,
|
||||||
|
filterDiffFileCreations,
|
||||||
hasNumberOfCodeBlocksIncreased,
|
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": "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\"",
|
"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",
|
"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"
|
"generate-beta-commit": "node ./development/generate-beta-commit.js"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
Loading…
Reference in New Issue
Block a user