mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Build: Lint files after removing their code fences (#12075)
* Add linting * Type the eslintInstance variable * Update documentation
This commit is contained in:
parent
2b1e05f086
commit
2b104603d5
@ -1,5 +1,5 @@
|
||||
# Development
|
||||
|
||||
Several files which are needed for developing on(!) MetaMask.
|
||||
Several files which are needed for developing on MetaMask.
|
||||
|
||||
Usually each files contains information about its scope / usage.
|
||||
Usually each file or directory contains information about its scope / usage.
|
||||
|
@ -3,7 +3,7 @@
|
||||
> _tl;dr_ `yarn dist` for prod, `yarn start` for local development
|
||||
|
||||
This directory contains the MetaMask build system, which is used to build the MetaMask Extension such that it can be used in a supported browser.
|
||||
From the repository root, the build system entry file is located at `development/build/index.js`.
|
||||
From the repository root, the build system entry file is located at [`./development/build/index.js`](https://github.com/MetaMask/metamask-extension/blob/develop/development/build/index.js).
|
||||
|
||||
Several package scripts invoke the build system.
|
||||
For example, `yarn start` creates a watched development build, and `yarn dist` creates a production build.
|
||||
@ -13,7 +13,17 @@ For local development, building without `lavamoat` is faster and therefore prefe
|
||||
The build system is not a full-featured CLI, but rather a script that expects some command line arguments and environment variables.
|
||||
For instructions regarding environment variables, see [the main repository readme](../../README.md#building-locally).
|
||||
|
||||
Here follows basic usage information for the build system.
|
||||
Generally speaking, the build system consists of [`gulp`](https://npmjs.com/package/gulp) tasks that either manipulate static assets or bundle source files using [Browserify](https://browserify.org/).
|
||||
Production-ready zip files are written to the `./builds` directory, while "unpacked" extension builds
|
||||
are written to the `./dist` directory.
|
||||
|
||||
Our JavaScript source files are transformed using [Babel](https://babeljs.io/), specifically using
|
||||
the [`babelify`](https://npmjs.com/package/babelify) Browserify transform.
|
||||
Source file bundling tasks are implemented in the [`./development/build/scripts.js`](https://github.com/MetaMask/metamask-extension/blob/develop/development/build/scripts.js).
|
||||
|
||||
> Locally implemented Browserify transforms, _some of which affect how we write JavaScript_, are listed and documented [here](./transforms/README.md).
|
||||
|
||||
## Usage
|
||||
|
||||
```text
|
||||
Usage: yarn build <entry-task> [options]
|
||||
@ -30,15 +40,21 @@ Commands:
|
||||
e2e tests.
|
||||
|
||||
Options:
|
||||
--beta-version If the build type is "beta", the beta version number.
|
||||
--beta-version If the build type is "beta", the beta version number.
|
||||
[number] [default: 0]
|
||||
--build-type The "type" of build to create. One of: "beta", "main"
|
||||
--build-type The "type" of build to create. One of: "beta", "main"
|
||||
[string] [default: "main"]
|
||||
--omit-lockdown Whether to omit SES lockdown files from the extension
|
||||
bundle. Useful when linking dependencies that are
|
||||
incompatible with lockdown.
|
||||
--lint-fence-files Whether files with code fences should be linted after
|
||||
fences have been removed by the code fencing transform.
|
||||
The build will fail if linting fails.
|
||||
Defaults to `false` if the entry task is `dev` or
|
||||
`testDev`, and `true` otherwise.
|
||||
[boolean] [default: <varies>]
|
||||
--omit-lockdown Whether to omit SES lockdown files from the extension
|
||||
bundle. Useful when linking dependencies that are
|
||||
incompatible with lockdown.
|
||||
[boolean] [default: false]
|
||||
--skip-stats Whether to refrain from logging build progress. Mostly used
|
||||
internally.
|
||||
--skip-stats Whether to refrain from logging build progress. Mostly
|
||||
used internally.
|
||||
[boolean] [default: false]
|
||||
```
|
||||
|
@ -40,6 +40,7 @@ function defineAndRunBuildTasks() {
|
||||
isBeta,
|
||||
isLavaMoat,
|
||||
shouldIncludeLockdown,
|
||||
shouldLintFenceFiles,
|
||||
skipStats,
|
||||
} = parseArgv();
|
||||
|
||||
@ -74,6 +75,7 @@ function defineAndRunBuildTasks() {
|
||||
buildType,
|
||||
isLavaMoat,
|
||||
livereload,
|
||||
shouldLintFenceFiles,
|
||||
});
|
||||
|
||||
const { clean, reload, zip } = createEtcTasks({
|
||||
@ -146,16 +148,22 @@ function parseArgv() {
|
||||
const NamedArgs = {
|
||||
BetaVersion: 'beta-version',
|
||||
BuildType: 'build-type',
|
||||
LintFenceFiles: 'lint-fence-files',
|
||||
OmitLockdown: 'omit-lockdown',
|
||||
SkipStats: 'skip-stats',
|
||||
};
|
||||
|
||||
const argv = minimist(process.argv.slice(2), {
|
||||
boolean: [NamedArgs.OmitLockdown, NamedArgs.SkipStats],
|
||||
boolean: [
|
||||
NamedArgs.LintFenceFiles,
|
||||
NamedArgs.OmitLockdown,
|
||||
NamedArgs.SkipStats,
|
||||
],
|
||||
string: [NamedArgs.BuildType],
|
||||
default: {
|
||||
[NamedArgs.BetaVersion]: 0,
|
||||
[NamedArgs.BuildType]: BuildTypes.main,
|
||||
[NamedArgs.LintFenceFiles]: true,
|
||||
[NamedArgs.OmitLockdown]: false,
|
||||
[NamedArgs.SkipStats]: false,
|
||||
},
|
||||
@ -182,6 +190,13 @@ function parseArgv() {
|
||||
throw new Error(`MetaMask build: Invalid build type: "${buildType}"`);
|
||||
}
|
||||
|
||||
// Manually default this to `false` for dev builds only.
|
||||
const shouldLintFenceFiles = process.argv.includes(
|
||||
`--${NamedArgs.LintFenceFiles}`,
|
||||
)
|
||||
? argv[NamedArgs.LintFenceFiles]
|
||||
: !/dev/iu.test(entryTask);
|
||||
|
||||
return {
|
||||
betaVersion: String(betaVersion),
|
||||
buildType,
|
||||
@ -189,6 +204,7 @@ function parseArgv() {
|
||||
isBeta: argv[NamedArgs.BuildType] === BuildTypes.beta,
|
||||
isLavaMoat: process.argv[0].includes('lavamoat'),
|
||||
shouldIncludeLockdown: argv[NamedArgs.OmitLockdown],
|
||||
shouldLintFenceFiles,
|
||||
skipStats: argv[NamedArgs.SkipStats],
|
||||
};
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ function createScriptTasks({
|
||||
buildType,
|
||||
isLavaMoat,
|
||||
livereload,
|
||||
shouldLintFenceFiles,
|
||||
}) {
|
||||
// internal tasks
|
||||
const core = {
|
||||
@ -97,6 +98,7 @@ function createScriptTasks({
|
||||
return `./app/scripts/${label}.js`;
|
||||
}),
|
||||
testing,
|
||||
shouldLintFenceFiles,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -151,6 +153,7 @@ function createScriptTasks({
|
||||
runInChildProcess(subtask, {
|
||||
buildType,
|
||||
isLavaMoat,
|
||||
shouldLintFenceFiles,
|
||||
}),
|
||||
);
|
||||
// make a parent task that runs each task in a child thread
|
||||
@ -166,6 +169,7 @@ function createScriptTasks({
|
||||
devMode,
|
||||
entryFilepath: `./app/scripts/${label}.js`,
|
||||
label,
|
||||
shouldLintFenceFiles,
|
||||
});
|
||||
}
|
||||
|
||||
@ -178,6 +182,7 @@ function createScriptTasks({
|
||||
devMode,
|
||||
entryFilepath: `./app/scripts/${label}.js`,
|
||||
label,
|
||||
shouldLintFenceFiles,
|
||||
});
|
||||
}
|
||||
|
||||
@ -190,6 +195,7 @@ function createScriptTasks({
|
||||
devMode,
|
||||
entryFilepath: `./app/scripts/${label}.js`,
|
||||
label,
|
||||
shouldLintFenceFiles,
|
||||
});
|
||||
}
|
||||
|
||||
@ -206,6 +212,7 @@ function createScriptTasks({
|
||||
entryFilepath: `./app/scripts/${inpage}.js`,
|
||||
label: inpage,
|
||||
testing,
|
||||
shouldLintFenceFiles,
|
||||
}),
|
||||
createNormalBundle({
|
||||
buildType,
|
||||
@ -215,6 +222,7 @@ function createScriptTasks({
|
||||
entryFilepath: `./app/scripts/${contentscript}.js`,
|
||||
label: contentscript,
|
||||
testing,
|
||||
shouldLintFenceFiles,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -226,6 +234,7 @@ function createFactoredBuild({
|
||||
devMode,
|
||||
entryFiles,
|
||||
testing,
|
||||
shouldLintFenceFiles,
|
||||
}) {
|
||||
return async function () {
|
||||
// create bundler setup and apply defaults
|
||||
@ -244,6 +253,7 @@ function createFactoredBuild({
|
||||
envVars,
|
||||
minify,
|
||||
reloadOnChange,
|
||||
shouldLintFenceFiles,
|
||||
});
|
||||
|
||||
// set bundle entries
|
||||
@ -344,6 +354,7 @@ function createNormalBundle({
|
||||
extraEntries = [],
|
||||
label,
|
||||
modulesToExpose,
|
||||
shouldLintFenceFiles,
|
||||
testing,
|
||||
}) {
|
||||
return async function () {
|
||||
@ -363,6 +374,7 @@ function createNormalBundle({
|
||||
envVars,
|
||||
minify,
|
||||
reloadOnChange,
|
||||
shouldLintFenceFiles,
|
||||
});
|
||||
|
||||
// set bundle entries
|
||||
@ -409,7 +421,7 @@ function createBuildConfiguration() {
|
||||
|
||||
function setupBundlerDefaults(
|
||||
buildConfiguration,
|
||||
{ buildType, devMode, envVars, minify, reloadOnChange },
|
||||
{ buildType, devMode, envVars, minify, reloadOnChange, shouldLintFenceFiles },
|
||||
) {
|
||||
const { bundlerOpts } = buildConfiguration;
|
||||
|
||||
@ -417,7 +429,7 @@ function setupBundlerDefaults(
|
||||
// Source transforms
|
||||
transform: [
|
||||
// Remove code that should be excluded from builds of the current type
|
||||
createRemoveFencedCodeTransform(buildType),
|
||||
createRemoveFencedCodeTransform(buildType, shouldLintFenceFiles),
|
||||
// Transpile top-level code
|
||||
babelify,
|
||||
// Inline `fs.readFileSync` files
|
||||
|
@ -48,7 +48,10 @@ function createTask(taskName, taskFn) {
|
||||
return task;
|
||||
}
|
||||
|
||||
function runInChildProcess(task, { buildType, isLavaMoat }) {
|
||||
function runInChildProcess(
|
||||
task,
|
||||
{ buildType, isLavaMoat, shouldLintFenceFiles },
|
||||
) {
|
||||
const taskName = typeof task === 'string' ? task : task.taskName;
|
||||
if (!taskName) {
|
||||
throw new Error(
|
||||
@ -63,7 +66,15 @@ function runInChildProcess(task, { buildType, isLavaMoat }) {
|
||||
if (isLavaMoat) {
|
||||
childProcess = spawn(
|
||||
'yarn',
|
||||
['build', taskName, '--build-type', buildType, '--skip-stats'],
|
||||
[
|
||||
'build',
|
||||
taskName,
|
||||
'--build-type',
|
||||
buildType,
|
||||
'--lint-fence-files',
|
||||
shouldLintFenceFiles,
|
||||
'--skip-stats',
|
||||
],
|
||||
{
|
||||
env: process.env,
|
||||
},
|
||||
@ -71,7 +82,15 @@ function runInChildProcess(task, { buildType, isLavaMoat }) {
|
||||
} else {
|
||||
childProcess = spawn(
|
||||
'yarn',
|
||||
['build:dev', taskName, '--build-type', buildType, '--skip-stats'],
|
||||
[
|
||||
'build:dev',
|
||||
taskName,
|
||||
'--build-type',
|
||||
buildType,
|
||||
'--lint-fence-files',
|
||||
shouldLintFenceFiles,
|
||||
'--skip-stats',
|
||||
],
|
||||
{
|
||||
env: process.env,
|
||||
},
|
||||
|
@ -52,6 +52,13 @@ commands inside the parameter parentheses:
|
||||
///: BEGIN:ONLY_INCLUDE_IN(beta,flask)
|
||||
```
|
||||
|
||||
### Gotchas
|
||||
|
||||
By default, the transform will invoke ESLint on files that are modified by the transform.
|
||||
This is our first line of defense against creating unsyntactic code using code fences, and the transform will error if linting fails.
|
||||
(Live reloading will continue to work if enabled.)
|
||||
To toggle this behavior via build system arguments, see [the build system readme](../README.md).
|
||||
|
||||
### Code Fencing Syntax
|
||||
|
||||
> In the specification, angle brackets, `< >`, indicate required tokens, while
|
||||
|
@ -1,6 +1,7 @@
|
||||
const path = require('path');
|
||||
const { PassThrough, Transform } = require('stream');
|
||||
const { BuildTypes } = require('../utils');
|
||||
const { lintTransformedFile } = require('./utils');
|
||||
|
||||
const hasOwnProperty = (obj, key) => Reflect.hasOwnProperty.call(obj, key);
|
||||
|
||||
@ -14,13 +15,18 @@ class RemoveFencedCodeTransform extends Transform {
|
||||
* A transform stream that calls {@link removeFencedCode} on the complete
|
||||
* string contents of the file read by Browserify.
|
||||
*
|
||||
* Optionally lints the file if it was modified.
|
||||
*
|
||||
* @param {string} filePath - The path to the file being transformed.
|
||||
* @param {string} buildType - The type of the current build process.env.
|
||||
* @param {string} buildType - The type of the current build process.
|
||||
* @param {boolean} shouldLintTransformedFiles - Whether the file should be
|
||||
* linted if modified by the transform.
|
||||
*/
|
||||
constructor(filePath, buildType) {
|
||||
constructor(filePath, buildType, shouldLintTransformedFiles) {
|
||||
super();
|
||||
this.filePath = filePath;
|
||||
this.buildType = buildType;
|
||||
this.shouldLintTransformedFiles = shouldLintTransformedFiles;
|
||||
this._fileBuffers = [];
|
||||
}
|
||||
|
||||
@ -35,14 +41,24 @@ class RemoveFencedCodeTransform extends Transform {
|
||||
// stream, immediately before the "end" event is emitted.
|
||||
// It applies the transform to the concatenated file contents.
|
||||
_flush(end) {
|
||||
const [fileContent] = removeFencedCode(
|
||||
const [fileContent, didModify] = removeFencedCode(
|
||||
this.filePath,
|
||||
this.buildType,
|
||||
Buffer.concat(this._fileBuffers).toString('utf8'),
|
||||
);
|
||||
|
||||
this.push(fileContent);
|
||||
end();
|
||||
const pushAndEnd = () => {
|
||||
this.push(fileContent);
|
||||
end();
|
||||
};
|
||||
|
||||
if (this.shouldLintTransformedFiles && didModify) {
|
||||
lintTransformedFile(fileContent, this.filePath)
|
||||
.then(pushAndEnd)
|
||||
.catch((error) => end(error));
|
||||
} else {
|
||||
pushAndEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,10 +73,19 @@ class RemoveFencedCodeTransform extends Transform {
|
||||
* For details on how the transform mutates source files, see
|
||||
* {@link removeFencedCode} and the documentation.
|
||||
*
|
||||
* If specified (and by default), the transform will call ESLint on the text
|
||||
* contents of any file that it modifies. The transform will error if such a
|
||||
* file is ignored by ESLint, since linting is our first line of defense against
|
||||
* making un-syntactic modifications to files using code fences.
|
||||
*
|
||||
* @param {string} buildType - The type of the current build.
|
||||
* @param {boolean} shouldLintTransformedFiles - Whether to lint transformed files.
|
||||
* @returns {(filePath: string) => Transform} The transform function.
|
||||
*/
|
||||
function createRemoveFencedCodeTransform(buildType) {
|
||||
function createRemoveFencedCodeTransform(
|
||||
buildType,
|
||||
shouldLintTransformedFiles = true,
|
||||
) {
|
||||
if (!hasOwnProperty(BuildTypes, buildType)) {
|
||||
throw new Error(
|
||||
`Code fencing transform received unrecognized build type "${buildType}".`,
|
||||
@ -81,7 +106,11 @@ function createRemoveFencedCodeTransform(buildType) {
|
||||
return new PassThrough();
|
||||
}
|
||||
|
||||
return new RemoveFencedCodeTransform(filePath, buildType);
|
||||
return new RemoveFencedCodeTransform(
|
||||
filePath,
|
||||
buildType,
|
||||
shouldLintTransformedFiles,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@ -156,7 +185,7 @@ const directiveParsingRegex = /^([A-Z]+):([A-Z_]+)(?:\(((?:\w+,)*\w+)\))?$/u;
|
||||
* For details, please see the documentation.
|
||||
*
|
||||
* @param {string} filePath - The path to the file being transformed.
|
||||
* @param {string} typeOfCurrentBuild - The type of the current build process.
|
||||
* @param {string} typeOfCurrentBuild - The type of the current build.
|
||||
* @param {string} fileContent - The contents of the file being transformed.
|
||||
* @returns {[string, modified]} A tuple of the post-transform file contents and
|
||||
* a boolean indicating whether they were modified.
|
||||
|
@ -4,6 +4,11 @@ const {
|
||||
createRemoveFencedCodeTransform,
|
||||
removeFencedCode,
|
||||
} = require('./remove-fenced-code');
|
||||
const transformUtils = require('./utils');
|
||||
|
||||
jest.mock('./utils', () => ({
|
||||
lintTransformedFile: jest.fn(),
|
||||
}));
|
||||
|
||||
// The test data is just strings. We get it from a function at the end of this
|
||||
// file because it takes up a lot of lines and is very distracting.
|
||||
@ -17,8 +22,13 @@ Conditionally_Included
|
||||
|
||||
describe('build/transforms/remove-fenced-code', () => {
|
||||
describe('createRemoveFencedCodeTransform', () => {
|
||||
const { lintTransformedFile: lintTransformedFileMock } = transformUtils;
|
||||
const mockJsFileName = 'file.js';
|
||||
|
||||
beforeEach(() => {
|
||||
lintTransformedFileMock.mockImplementation(() => Promise.resolve());
|
||||
});
|
||||
|
||||
it('rejects invalid build types', () => {
|
||||
expect(() => createRemoveFencedCodeTransform('foobar')).toThrow(
|
||||
/received unrecognized build type "foobar".$/u,
|
||||
@ -37,6 +47,7 @@ describe('build/transforms/remove-fenced-code', () => {
|
||||
|
||||
stream.on('end', () => {
|
||||
expect(streamOutput).toStrictEqual(fileContent);
|
||||
expect(lintTransformedFileMock).not.toHaveBeenCalled();
|
||||
resolve();
|
||||
});
|
||||
|
||||
@ -59,6 +70,11 @@ describe('build/transforms/remove-fenced-code', () => {
|
||||
|
||||
stream.on('end', () => {
|
||||
expect(streamOutput).toStrictEqual(filePrefix);
|
||||
expect(lintTransformedFileMock).toHaveBeenCalledTimes(1);
|
||||
expect(lintTransformedFileMock).toHaveBeenCalledWith(
|
||||
filePrefix,
|
||||
mockJsFileName,
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
|
||||
@ -86,6 +102,11 @@ describe('build/transforms/remove-fenced-code', () => {
|
||||
|
||||
stream.on('end', () => {
|
||||
expect(streamOutput).toStrictEqual(filePrefix);
|
||||
expect(lintTransformedFileMock).toHaveBeenCalledTimes(1);
|
||||
expect(lintTransformedFileMock).toHaveBeenCalledWith(
|
||||
filePrefix,
|
||||
mockJsFileName,
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
|
||||
@ -107,6 +128,57 @@ describe('build/transforms/remove-fenced-code', () => {
|
||||
|
||||
stream.on('end', () => {
|
||||
expect(streamOutput).toStrictEqual(fileContent);
|
||||
expect(lintTransformedFileMock).not.toHaveBeenCalled();
|
||||
resolve();
|
||||
});
|
||||
|
||||
stream.end(fileContent);
|
||||
});
|
||||
});
|
||||
|
||||
it('skips linting for transformed file if shouldLintTransformedFiles is false', async () => {
|
||||
const filePrefix = '// A comment\n';
|
||||
const fileContent = filePrefix.concat(getMinimalFencedCode());
|
||||
|
||||
const stream = createRemoveFencedCodeTransform(
|
||||
'main',
|
||||
false,
|
||||
)(mockJsFileName);
|
||||
let streamOutput = '';
|
||||
|
||||
await new Promise((resolve) => {
|
||||
stream.on('data', (data) => {
|
||||
streamOutput = streamOutput.concat(data.toString('utf8'));
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
expect(streamOutput).toStrictEqual(filePrefix);
|
||||
expect(lintTransformedFileMock).not.toHaveBeenCalled();
|
||||
resolve();
|
||||
});
|
||||
|
||||
stream.end(fileContent);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles transformed file lint failure', async () => {
|
||||
lintTransformedFileMock.mockImplementationOnce(() =>
|
||||
Promise.reject(new Error('lint failure')),
|
||||
);
|
||||
|
||||
const filePrefix = '// A comment\n';
|
||||
const fileContent = filePrefix.concat(getMinimalFencedCode());
|
||||
|
||||
const stream = createRemoveFencedCodeTransform('main')(mockJsFileName);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
stream.on('error', (error) => {
|
||||
expect(error).toStrictEqual(new Error('lint failure'));
|
||||
expect(lintTransformedFileMock).toHaveBeenCalledTimes(1);
|
||||
expect(lintTransformedFileMock).toHaveBeenCalledWith(
|
||||
filePrefix,
|
||||
mockJsFileName,
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
|
||||
|
68
development/build/transforms/utils.js
Normal file
68
development/build/transforms/utils.js
Normal file
@ -0,0 +1,68 @@
|
||||
const { ESLint } = require('eslint');
|
||||
const eslintrc = require('../../../.eslintrc.js');
|
||||
|
||||
/**
|
||||
* The singleton ESLint instance.
|
||||
*
|
||||
* @type {ESLint}
|
||||
*/
|
||||
let eslintInstance;
|
||||
|
||||
// We only need a single ESLint instance, and we only initialize it if necessary
|
||||
const initializeESLint = () => {
|
||||
if (!eslintInstance) {
|
||||
eslintInstance = new ESLint({ baseConfig: eslintrc, useEslintrc: false });
|
||||
}
|
||||
};
|
||||
|
||||
// Four spaces
|
||||
const TAB = ' ';
|
||||
|
||||
module.exports = {
|
||||
lintTransformedFile,
|
||||
};
|
||||
|
||||
/**
|
||||
* Lints a transformed file by invoking ESLint programmatically on the string
|
||||
* file contents. The path to the file must be specified so that the repository
|
||||
* ESLint config can be applied properly.
|
||||
*
|
||||
* An error is thrown if linting produced any errors, or if the file is ignored
|
||||
* by ESLint. Files linted by this function should never be ignored.
|
||||
*
|
||||
* @param {string} content - The file content.
|
||||
* @param {string} filePath - The path to the file.
|
||||
* @returns {Promise<void>} Returns `undefined` or throws an error if linting produced
|
||||
* any errors, or if the linted file is ignored.
|
||||
*/
|
||||
async function lintTransformedFile(content, filePath) {
|
||||
initializeESLint();
|
||||
|
||||
const lintResult = (
|
||||
await eslintInstance.lintText(content, { filePath, warnIgnored: false })
|
||||
)[0];
|
||||
|
||||
// This indicates that the file is ignored, which should never be the case for
|
||||
// a transformed file.
|
||||
if (lintResult === undefined) {
|
||||
throw new Error(
|
||||
`MetaMask build: Transformed file "${filePath}" appears to be ignored by ESLint.`,
|
||||
);
|
||||
}
|
||||
|
||||
// This is the success case
|
||||
if (lintResult.errorCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Errors are stored in the messages array, and their "severity" is 2
|
||||
const errorsString = lintResult.messages
|
||||
.filter(({ severity }) => severity === 2)
|
||||
.reduce((allErrors, { message, ruleId }) => {
|
||||
return allErrors.concat(`${TAB}${ruleId}\n${TAB}${message}\n\n`);
|
||||
}, '');
|
||||
|
||||
throw new Error(
|
||||
`MetaMask build: Lint errors encountered for transformed file "${filePath}":\n\n${errorsString}`,
|
||||
);
|
||||
}
|
71
development/build/transforms/utils.test.js
Normal file
71
development/build/transforms/utils.test.js
Normal file
@ -0,0 +1,71 @@
|
||||
const { lintTransformedFile } = require('./utils');
|
||||
|
||||
let mockESLint;
|
||||
|
||||
jest.mock('eslint', () => ({
|
||||
ESLint: class MockESLint {
|
||||
constructor() {
|
||||
if (mockESLint) {
|
||||
throw new Error('Mock ESLint ref already assigned!');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line consistent-this
|
||||
mockESLint = this;
|
||||
|
||||
// eslint-disable-next-line jest/prefer-spy-on
|
||||
this.lintText = jest.fn();
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
describe('transform utils', () => {
|
||||
describe('lintTransformedFile', () => {
|
||||
it('initializes the ESLint singleton', async () => {
|
||||
expect(mockESLint).not.toBeDefined();
|
||||
|
||||
// This error is an artifact of how we're mocking the ESLint singleton,
|
||||
// and won't actually occur in production.
|
||||
await expect(() => lintTransformedFile()).rejects.toThrow(
|
||||
`Cannot read property '0' of undefined`,
|
||||
);
|
||||
expect(mockESLint).toBeDefined();
|
||||
});
|
||||
|
||||
it('returns if linting passes with no errors', async () => {
|
||||
mockESLint.lintText.mockImplementationOnce(() =>
|
||||
Promise.resolve([{ errorCount: 0 }]),
|
||||
);
|
||||
|
||||
expect(
|
||||
await lintTransformedFile('/* JavaScript */', 'file.js'),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('throws if the file is ignored by ESLint', async () => {
|
||||
mockESLint.lintText.mockImplementationOnce(() => Promise.resolve([]));
|
||||
|
||||
await expect(() =>
|
||||
lintTransformedFile('/* JavaScript */', 'file.js'),
|
||||
).rejects.toThrow(
|
||||
/Transformed file "file\.js" appears to be ignored by ESLint\.$/u,
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if linting produced any errors', async () => {
|
||||
const ruleId = 'some-eslint-rule';
|
||||
const message = 'You violated the rule!';
|
||||
|
||||
mockESLint.lintText.mockImplementationOnce(() =>
|
||||
Promise.resolve([
|
||||
{ errorCount: 1, messages: [{ message, ruleId, severity: 2 }] },
|
||||
]),
|
||||
);
|
||||
|
||||
await expect(() =>
|
||||
lintTransformedFile('/* JavaScript */', 'file.js'),
|
||||
).rejects.toThrow(
|
||||
/Lint errors encountered for transformed file "file\.js":\n\n {4}some-eslint-rule\n {4}You violated the rule!\n\n$/u,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -785,6 +785,42 @@
|
||||
"console.log": true
|
||||
}
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"builtin": {
|
||||
"assert": true,
|
||||
"fs.existsSync": true,
|
||||
"fs.readFileSync": true,
|
||||
"module.createRequire": true,
|
||||
"module.createRequireFromPath": true,
|
||||
"os.homedir": true,
|
||||
"path.basename": true,
|
||||
"path.dirname": true,
|
||||
"path.extname": true,
|
||||
"path.isAbsolute": true,
|
||||
"path.join": true,
|
||||
"path.relative": true,
|
||||
"path.resolve": true,
|
||||
"path.sep": true,
|
||||
"util.inspect": true
|
||||
},
|
||||
"globals": {
|
||||
"__dirname": true,
|
||||
"process.cwd": true,
|
||||
"process.emitWarning": true,
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"ajv": true,
|
||||
"debug": true,
|
||||
"espree": true,
|
||||
"globals": true,
|
||||
"ignore": true,
|
||||
"import-fresh": true,
|
||||
"js-yaml": true,
|
||||
"minimatch": true,
|
||||
"strip-json-comments": true
|
||||
}
|
||||
},
|
||||
"@gulp-sourcemaps/identity-map": {
|
||||
"packages": {
|
||||
"acorn": true,
|
||||
@ -897,6 +933,11 @@
|
||||
"acorn": true
|
||||
}
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"packages": {
|
||||
"acorn": true
|
||||
}
|
||||
},
|
||||
"acorn-node": {
|
||||
"packages": {
|
||||
"acorn": true,
|
||||
@ -911,8 +952,14 @@
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"globals": {
|
||||
"console": true
|
||||
},
|
||||
"packages": {
|
||||
"fast-deep-equal": true
|
||||
"fast-deep-equal": true,
|
||||
"fast-json-stable-stringify": true,
|
||||
"json-schema-traverse": true,
|
||||
"uri-js": true
|
||||
}
|
||||
},
|
||||
"amdefine": {
|
||||
@ -970,6 +1017,16 @@
|
||||
"buffer-equal": true
|
||||
}
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"builtin": {
|
||||
"events.EventEmitter": true,
|
||||
"util.inherits": true
|
||||
},
|
||||
"packages": {
|
||||
"delegates": true,
|
||||
"readable-stream": true
|
||||
}
|
||||
},
|
||||
"arr-diff": {
|
||||
"packages": {
|
||||
"arr-flatten": true,
|
||||
@ -1329,6 +1386,7 @@
|
||||
"anymatch": true,
|
||||
"async-each": true,
|
||||
"braces": true,
|
||||
"fsevents": true,
|
||||
"glob-parent": true,
|
||||
"inherits": true,
|
||||
"is-binary-path": true,
|
||||
@ -1580,6 +1638,16 @@
|
||||
"through2": true
|
||||
}
|
||||
},
|
||||
"detect-libc": {
|
||||
"builtin": {
|
||||
"child_process.spawnSync": true,
|
||||
"fs.readdirSync": true,
|
||||
"os.platform": true
|
||||
},
|
||||
"globals": {
|
||||
"process.env": true
|
||||
}
|
||||
},
|
||||
"detective": {
|
||||
"packages": {
|
||||
"acorn-node": true,
|
||||
@ -1600,6 +1668,14 @@
|
||||
"path-type": true
|
||||
}
|
||||
},
|
||||
"doctrine": {
|
||||
"builtin": {
|
||||
"assert": true
|
||||
},
|
||||
"packages": {
|
||||
"esutils": true
|
||||
}
|
||||
},
|
||||
"dom-serializer": {
|
||||
"packages": {
|
||||
"domelementtype": true,
|
||||
@ -1729,6 +1805,92 @@
|
||||
"source-map": true
|
||||
}
|
||||
},
|
||||
"eslint": {
|
||||
"builtin": {
|
||||
"assert": true,
|
||||
"fs.existsSync": true,
|
||||
"fs.lstatSync": true,
|
||||
"fs.readFileSync": true,
|
||||
"fs.readdirSync": true,
|
||||
"fs.statSync": true,
|
||||
"fs.unlinkSync": true,
|
||||
"fs.writeFile": true,
|
||||
"fs.writeFileSync": true,
|
||||
"path.extname": true,
|
||||
"path.isAbsolute": true,
|
||||
"path.join": true,
|
||||
"path.normalize": true,
|
||||
"path.relative": true,
|
||||
"path.resolve": true,
|
||||
"path.sep": true,
|
||||
"util.format": true,
|
||||
"util.inspect": true,
|
||||
"util.promisify": true
|
||||
},
|
||||
"globals": {
|
||||
"__dirname": true,
|
||||
"console.log": true,
|
||||
"describe": true,
|
||||
"it": true,
|
||||
"process": true
|
||||
},
|
||||
"packages": {
|
||||
"@eslint/eslintrc": true,
|
||||
"ajv": true,
|
||||
"debug": true,
|
||||
"doctrine": true,
|
||||
"eslint-scope": true,
|
||||
"eslint-utils": true,
|
||||
"eslint-visitor-keys": true,
|
||||
"espree": true,
|
||||
"esquery": true,
|
||||
"esutils": true,
|
||||
"file-entry-cache": true,
|
||||
"functional-red-black-tree": true,
|
||||
"glob-parent": true,
|
||||
"globals": true,
|
||||
"ignore": true,
|
||||
"imurmurhash": true,
|
||||
"is-glob": true,
|
||||
"json-stable-stringify-without-jsonify": true,
|
||||
"levn": true,
|
||||
"lodash": true,
|
||||
"minimatch": true,
|
||||
"natural-compare": true,
|
||||
"regexpp": true
|
||||
}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"builtin": {
|
||||
"assert": true
|
||||
},
|
||||
"packages": {
|
||||
"esrecurse": true,
|
||||
"estraverse": true
|
||||
}
|
||||
},
|
||||
"eslint-utils": {
|
||||
"packages": {
|
||||
"eslint-visitor-keys": true
|
||||
}
|
||||
},
|
||||
"espree": {
|
||||
"packages": {
|
||||
"acorn": true,
|
||||
"acorn-jsx": true,
|
||||
"eslint-visitor-keys": true
|
||||
}
|
||||
},
|
||||
"esquery": {
|
||||
"globals": {
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"esrecurse": {
|
||||
"packages": {
|
||||
"estraverse": true
|
||||
}
|
||||
},
|
||||
"event-emitter": {
|
||||
"packages": {
|
||||
"d": true,
|
||||
@ -1911,7 +2073,9 @@
|
||||
"flat-cache": {
|
||||
"builtin": {
|
||||
"fs.existsSync": true,
|
||||
"fs.mkdirSync": true,
|
||||
"fs.readFileSync": true,
|
||||
"fs.writeFileSync": true,
|
||||
"path.basename": true,
|
||||
"path.dirname": true,
|
||||
"path.resolve": true
|
||||
@ -2020,6 +2184,45 @@
|
||||
"process.version": true
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"builtin": {
|
||||
"events.EventEmitter": true,
|
||||
"fs.stat": true,
|
||||
"path.join": true,
|
||||
"util.inherits": true
|
||||
},
|
||||
"globals": {
|
||||
"__dirname": true,
|
||||
"process.nextTick": true,
|
||||
"process.platform": true,
|
||||
"setImmediate": true
|
||||
},
|
||||
"native": true,
|
||||
"packages": {
|
||||
"node-pre-gyp": true
|
||||
}
|
||||
},
|
||||
"gauge": {
|
||||
"builtin": {
|
||||
"util.format": true
|
||||
},
|
||||
"globals": {
|
||||
"clearInterval": true,
|
||||
"process": true,
|
||||
"setImmediate": true,
|
||||
"setInterval": true
|
||||
},
|
||||
"packages": {
|
||||
"aproba": true,
|
||||
"console-control-strings": true,
|
||||
"has-unicode": true,
|
||||
"object-assign": true,
|
||||
"signal-exit": true,
|
||||
"string-width": true,
|
||||
"strip-ansi": true,
|
||||
"wide-align": true
|
||||
}
|
||||
},
|
||||
"get-assigned-identifiers": {
|
||||
"builtin": {
|
||||
"assert.equal": true
|
||||
@ -2380,6 +2583,16 @@
|
||||
"process.argv": true
|
||||
}
|
||||
},
|
||||
"has-unicode": {
|
||||
"builtin": {
|
||||
"os.type": true
|
||||
},
|
||||
"globals": {
|
||||
"process.env.LANG": true,
|
||||
"process.env.LC_ALL": true,
|
||||
"process.env.LC_CTYPE": true
|
||||
}
|
||||
},
|
||||
"has-value": {
|
||||
"packages": {
|
||||
"get-value": true,
|
||||
@ -2533,6 +2746,11 @@
|
||||
"is-plain-object": true
|
||||
}
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"packages": {
|
||||
"number-is-nan": true
|
||||
}
|
||||
},
|
||||
"is-glob": {
|
||||
"packages": {
|
||||
"is-extglob": true
|
||||
@ -2611,6 +2829,11 @@
|
||||
"isarray": true
|
||||
}
|
||||
},
|
||||
"js-yaml": {
|
||||
"globals": {
|
||||
"esprima": true
|
||||
}
|
||||
},
|
||||
"jsesc": {
|
||||
"globals": {
|
||||
"Buffer.isBuffer": true
|
||||
@ -2688,6 +2911,12 @@
|
||||
"flush-write-stream": true
|
||||
}
|
||||
},
|
||||
"levn": {
|
||||
"packages": {
|
||||
"prelude-ls": true,
|
||||
"type-check": true
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"globals": {
|
||||
"define": true
|
||||
@ -2917,6 +3146,56 @@
|
||||
"setTimeout": true
|
||||
}
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"builtin": {
|
||||
"events.EventEmitter": true,
|
||||
"fs.existsSync": true,
|
||||
"fs.readFileSync": true,
|
||||
"fs.renameSync": true,
|
||||
"path.dirname": true,
|
||||
"path.existsSync": true,
|
||||
"path.join": true,
|
||||
"path.resolve": true,
|
||||
"url.parse": true,
|
||||
"url.resolve": true,
|
||||
"util.inherits": true
|
||||
},
|
||||
"globals": {
|
||||
"__dirname": true,
|
||||
"console.log": true,
|
||||
"process.arch": true,
|
||||
"process.cwd": true,
|
||||
"process.env": true,
|
||||
"process.platform": true,
|
||||
"process.version.substr": true,
|
||||
"process.versions": true
|
||||
},
|
||||
"packages": {
|
||||
"detect-libc": true,
|
||||
"nopt": true,
|
||||
"npmlog": true,
|
||||
"rimraf": true,
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"builtin": {
|
||||
"path": true,
|
||||
"stream.Stream": true,
|
||||
"url": true
|
||||
},
|
||||
"globals": {
|
||||
"console": true,
|
||||
"process.argv": true,
|
||||
"process.env.DEBUG_NOPT": true,
|
||||
"process.env.NOPT_DEBUG": true,
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"abbrev": true,
|
||||
"osenv": true
|
||||
}
|
||||
},
|
||||
"normalize-path": {
|
||||
"packages": {
|
||||
"remove-trailing-separator": true
|
||||
@ -2932,6 +3211,22 @@
|
||||
"once": true
|
||||
}
|
||||
},
|
||||
"npmlog": {
|
||||
"builtin": {
|
||||
"events.EventEmitter": true,
|
||||
"util": true
|
||||
},
|
||||
"globals": {
|
||||
"process.nextTick": true,
|
||||
"process.stderr": true
|
||||
},
|
||||
"packages": {
|
||||
"are-we-there-yet": true,
|
||||
"console-control-strings": true,
|
||||
"gauge": true,
|
||||
"set-blocking": true
|
||||
}
|
||||
},
|
||||
"object-copy": {
|
||||
"packages": {
|
||||
"copy-descriptor": true,
|
||||
@ -2998,6 +3293,54 @@
|
||||
"readable-stream": true
|
||||
}
|
||||
},
|
||||
"os-homedir": {
|
||||
"builtin": {
|
||||
"os.homedir": true
|
||||
},
|
||||
"globals": {
|
||||
"process.env": true,
|
||||
"process.getuid": true,
|
||||
"process.platform": true
|
||||
}
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"globals": {
|
||||
"process.env.SystemRoot": true,
|
||||
"process.env.TEMP": true,
|
||||
"process.env.TMP": true,
|
||||
"process.env.TMPDIR": true,
|
||||
"process.env.windir": true,
|
||||
"process.platform": true
|
||||
}
|
||||
},
|
||||
"osenv": {
|
||||
"builtin": {
|
||||
"child_process.exec": true,
|
||||
"path": true
|
||||
},
|
||||
"globals": {
|
||||
"process.env.COMPUTERNAME": true,
|
||||
"process.env.ComSpec": true,
|
||||
"process.env.EDITOR": true,
|
||||
"process.env.HOSTNAME": true,
|
||||
"process.env.PATH": true,
|
||||
"process.env.PROMPT": true,
|
||||
"process.env.PS1": true,
|
||||
"process.env.Path": true,
|
||||
"process.env.SHELL": true,
|
||||
"process.env.USER": true,
|
||||
"process.env.USERDOMAIN": true,
|
||||
"process.env.USERNAME": true,
|
||||
"process.env.VISUAL": true,
|
||||
"process.env.path": true,
|
||||
"process.nextTick": true,
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"os-homedir": true,
|
||||
"os-tmpdir": true
|
||||
}
|
||||
},
|
||||
"parent-module": {
|
||||
"packages": {
|
||||
"callsites": true
|
||||
@ -3580,6 +3923,12 @@
|
||||
"process": true
|
||||
}
|
||||
},
|
||||
"set-blocking": {
|
||||
"globals": {
|
||||
"process.stderr": true,
|
||||
"process.stdout": true
|
||||
}
|
||||
},
|
||||
"set-value": {
|
||||
"packages": {
|
||||
"extend-shallow": true,
|
||||
@ -3782,6 +4131,7 @@
|
||||
},
|
||||
"string-width": {
|
||||
"packages": {
|
||||
"code-point-at": true,
|
||||
"emoji-regex": true,
|
||||
"is-fullwidth-code-point": true,
|
||||
"strip-ansi": true
|
||||
@ -4040,6 +4390,11 @@
|
||||
"through2": true
|
||||
}
|
||||
},
|
||||
"type-check": {
|
||||
"packages": {
|
||||
"prelude-ls": true
|
||||
}
|
||||
},
|
||||
"typedarray-to-buffer": {
|
||||
"globals": {
|
||||
"Buffer.from": true
|
||||
@ -4145,6 +4500,11 @@
|
||||
"path": true
|
||||
}
|
||||
},
|
||||
"uri-js": {
|
||||
"globals": {
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"urix": {
|
||||
"builtin": {
|
||||
"path.sep": true
|
||||
@ -4348,6 +4708,11 @@
|
||||
"isexe": true
|
||||
}
|
||||
},
|
||||
"wide-align": {
|
||||
"packages": {
|
||||
"string-width": true
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"builtin": {
|
||||
"fs.createWriteStream": true,
|
||||
|
Loading…
Reference in New Issue
Block a user