Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ Support ESLint 8 #696

Merged
merged 7 commits into from
Apr 18, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const config = {
named: "never",
asyncArrow: "always"
}
]
],
"import/no-import-module-exports": "off"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated eslint-config-kentcdodds package is set to error on this rule.

}
};

Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
],
"license": "MIT",
"dependencies": {
"@typescript-eslint/parser": "^3.0.0",
"@typescript-eslint/parser": "^5.10.0",
"common-tags": "^1.4.0",
"dlv": "^1.1.0",
"eslint": "^7.9.0",
"eslint": "^8.7.0",
"indent-string": "^4.0.0",
"lodash.merge": "^4.6.0",
"loglevel-colored-level-prefix": "^1.0.0",
"prettier": "^2.0.0",
"prettier": "^2.5.1",
"pretty-format": "^23.0.1",
"require-relative": "^0.8.7",
"typescript": "^3.9.3",
"vue-eslint-parser": "~7.1.0"
"typescript": "^4.5.4",
"vue-eslint-parser": "^8.0.1"
},
"devDependencies": {
"@babel/cli": "^7.4.4",
Expand All @@ -40,7 +40,7 @@
"all-contributors-cli": "^6.7.0",
"babel-jest": "^25.0.0",
"chalk": "^2.1.0",
"eslint-config-kentcdodds": "~16.0.1",
"eslint-config-kentcdodds": "^20.0.1",
"husky": "^2.4.1",
"jest": "^25.0.0",
"jest-cli": "^25.0.0",
Expand Down
49 changes: 24 additions & 25 deletions src/__mocks__/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@
// search around the file system for stuff

const eslint = jest.requireActual('eslint');
const { CLIEngine } = eslint;
const { ESLint } = eslint;

const mockGetConfigForFileSpy = jest.fn(mockGetConfigForFile);
mockGetConfigForFileSpy.overrides = {};
const mockExecuteOnTextSpy = jest.fn(mockExecuteOnText);
const mockCalculateConfigForFileSpy = jest.fn(mockCalculateConfigForFile);
mockCalculateConfigForFileSpy.overrides = {};
const mockLintTextSpy = jest.fn(mockLintText);

module.exports = Object.assign(eslint, {
CLIEngine: jest.fn(MockCLIEngine),
ESLint: jest.fn(MockESLint),
mock: {
getConfigForFile: mockGetConfigForFileSpy,
executeOnText: mockExecuteOnTextSpy
calculateConfigForFile: mockCalculateConfigForFileSpy,
lintText: mockLintTextSpy
}
});

function MockCLIEngine(...args) {
function MockESLint(...args) {
global.__PRETTIER_ESLINT_TEST_STATE__.eslintPath = __filename;
const cliEngine = new CLIEngine(...args);
cliEngine.getConfigForFile = mockGetConfigForFileSpy;
cliEngine._originalExecuteOnText = cliEngine.executeOnText;
cliEngine.executeOnText = mockExecuteOnTextSpy;
return cliEngine;
const eslintInstance = new ESLint(...args);
eslintInstance.calculateConfigForFile = mockCalculateConfigForFileSpy;
eslintInstance._originalLintText = eslintInstance.lintText;
eslintInstance.lintText = mockLintTextSpy;
return eslintInstance;
}

MockCLIEngine.prototype = Object.create(CLIEngine.prototype);
MockESLint.prototype = Object.create(ESLint.prototype);

// eslint-disable-next-line complexity
function mockGetConfigForFile(filePath) {
if (mockGetConfigForFileSpy.throwError) {
throw mockGetConfigForFileSpy.throwError;
function mockCalculateConfigForFile(filePath) {
if (mockCalculateConfigForFileSpy.throwError) {
throw mockCalculateConfigForFileSpy.throwError;
}
if (!filePath) {
return {
Expand All @@ -39,7 +39,7 @@ function mockGetConfigForFile(filePath) {
}
if (filePath.includes('default-config')) {
return {
rules: {
rules: {
semi: [2, 'never'],
'max-len': [2, 120, 2],
indent: [2, 2, { SwitchCase: 1 }],
Expand All @@ -59,8 +59,7 @@ function mockGetConfigForFile(filePath) {
}
],
'arrow-parens': [2, 'as-needed']
}
};
}};
} else if (filePath.includes('fixtures/paths')) {
return { rules: {} };
} else {
Expand All @@ -71,10 +70,10 @@ function mockGetConfigForFile(filePath) {
}
}

function mockExecuteOnText(...args) {
/* eslint babel/no-invalid-this:0 */
if (mockExecuteOnTextSpy.throwError) {
throw mockExecuteOnTextSpy.throwError;
function mockLintText(...args) {
/* eslint no-invalid-this:0 */
if (mockLintTextSpy.throwError) {
throw mockLintTextSpy.throwError;
}
return this._originalExecuteOnText(...args);
return this._originalLintText(...args);
}
119 changes: 60 additions & 59 deletions src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ const tests = [
input: {
text: 'const { foo } = bar;',
eslintConfig: {
foo: true,
// Won't be overridden
parserOptions: {
ecmaVersion: 7
Expand Down Expand Up @@ -226,8 +225,8 @@ const tests = [
];

beforeEach(() => {
eslintMock.mock.executeOnText.mockClear();
eslintMock.mock.getConfigForFile.mockClear();
eslintMock.mock.lintText.mockClear();
eslintMock.mock.calculateConfigForFile.mockClear();
prettierMock.format.mockClear();
prettierMock.resolveConfig.sync.mockClear();
fsMock.readFileSync.mockClear();
Expand All @@ -240,55 +239,55 @@ tests.forEach(({ title, modifier, input, output }) => {
if (modifier) {
fn = test[modifier];
}
fn(title, () => {
fn(title, async () => {
input.text = stripIndent(input.text).trim();
const expected = stripIndent(output).trim();
const actual = format(input);
const actual = await format(input);
// adding the newline in the expected because
// prettier adds a newline to the end of the input
expect(actual).toBe(`${expected}\n`);
});
});

test('failure to fix with eslint throws and logs an error', () => {
const { executeOnText } = eslintMock.mock;
test('failure to fix with eslint throws and logs an error', async () => {
const { lintText } = eslintMock.mock;
const error = new Error('Something happened');
executeOnText.throwError = error;
lintText.throwError = error;

expect(() => format({ text: '' })).toThrowError(error);
await expect(() => format({ text: '' })).rejects.toThrowError(error);
expect(logger.error).toHaveBeenCalledTimes(1);
executeOnText.throwError = null;
lintText.throwError = null;
});

test('logLevel is used to configure the logger', () => {
test('logLevel is used to configure the logger', async () => {
logger.setLevel = jest.fn();
format({ text: '', logLevel: 'silent' });
await format({ text: '', logLevel: 'silent' });
expect(logger.setLevel).toHaveBeenCalledTimes(1);
expect(logger.setLevel).toHaveBeenCalledWith('silent');
});

test(`when prettier throws, log to logger.error and throw the error`, () => {
test(`when prettier throws, log to logger.error and throw the error`, async () => {
const error = new Error('something bad happened');
prettierMock.format.throwError = error;

expect(() => format({ text: '' })).toThrowError(error);
await expect(() => format({ text: '' })).rejects.toThrowError(error);
expect(logger.error).toHaveBeenCalledTimes(1);
prettierMock.format.throwError = null;
});

test('can accept a path to an eslint module and uses that instead.', () => {
test('can accept a path to an eslint module and uses that instead.', async () => {
const eslintPath = path.join(__dirname, '../__mocks__/eslint');
format({ text: '', eslintPath });
expect(eslintMock.mock.executeOnText).toHaveBeenCalledTimes(1);
await format({ text: '', eslintPath });
expect(eslintMock.mock.lintText).toHaveBeenCalledTimes(1);
});

test('fails with an error if the eslint module cannot be resolved.', () => {
test('fails with an error if the eslint module cannot be resolved.', async () => {
const eslintPath = path.join(
__dirname,
'../__mocks__/non-existent-eslint-module'
);

expect(() => format({ text: '', eslintPath })).toThrowError(
await expect(() => format({ text: '', eslintPath })).rejects.toThrowError(
/non-existent-eslint-module/
);
expect(logger.error).toHaveBeenCalledTimes(1);
Expand All @@ -300,19 +299,20 @@ test('fails with an error if the eslint module cannot be resolved.', () => {
expect(logger.error).toHaveBeenCalledWith(errorString);
});

test('can accept a path to a prettier module and uses that instead.', () => {
test('can accept a path to a prettier module and uses that instead.', async () => {
const prettierPath = path.join(__dirname, '../__mocks__/prettier');
format({ text: '', prettierPath });
await format({ text: '', prettierPath});

expect(prettierMock.format).toHaveBeenCalledTimes(1);
});

test('fails with an error if the prettier module cannot be resolved.', () => {
test('fails with an error if the prettier module cannot be resolved.', async () => {
const prettierPath = path.join(
__dirname,
'../__mocks__/non-existent-prettier-module'
);

expect(() => format({ text: '', prettierPath })).toThrowError(
await expect(() => format({ text: '', prettierPath })).rejects.toThrowError(
/non-existent-prettier-module/
);
expect(logger.error).toHaveBeenCalledTimes(1);
Expand All @@ -322,9 +322,9 @@ test('fails with an error if the prettier module cannot be resolved.', () => {
expect(logger.error).toHaveBeenCalledWith(errorString);
});

test('resolves to the eslint module relative to the given filePath', () => {
test('resolves to the eslint module relative to the given filePath', async () => {
const filePath = require.resolve('../../tests/fixtures/paths/foo.js');
format({ text: '', filePath });
await format({ text: '', filePath });
const stateObj = {
eslintPath: require.resolve(
'../../tests/fixtures/paths/node_modules/eslint/index.js'
Expand All @@ -336,9 +336,9 @@ test('resolves to the eslint module relative to the given filePath', () => {
expect(global.__PRETTIER_ESLINT_TEST_STATE__).toMatchObject(stateObj);
});

test('resolves to the local eslint module', () => {
test('resolves to the local eslint module', async () => {
const filePath = '/blah-blah/default-config.js';
format({ text: '', filePath });
await format({ text: '', filePath });
expect(global.__PRETTIER_ESLINT_TEST_STATE__).toMatchObject({
// without Jest's mocking, these would actually resolve to the
// project modules :) The fact that jest's mocking is being
Expand All @@ -348,31 +348,31 @@ test('resolves to the local eslint module', () => {
});
});

test('reads text from fs if filePath is provided but not text', () => {
test('reads text from fs if filePath is provided but not text', async () => {
const readFileSyncMockSpy = jest.spyOn(fsMock, 'readFileSync');

const filePath = '/blah-blah/some-file.js';
format({ filePath });
await format({ filePath });

expect(readFileSyncMockSpy).toHaveBeenCalledWith(filePath, 'utf8');

});

test('logs error if it cannot read the file from the filePath', () => {
test('logs error if it cannot read the file from the filePath', async () => {
const originalMock = fsMock.readFileSync;
fsMock.readFileSync = jest.fn(() => {
throw new Error('some error');
});
expect(() => format({ filePath: '/some-path.js' })).toThrowError(
await expect(() => format({ filePath: '/some-path.js' })).rejects.toThrowError(
/some error/
);
expect(logger.error).toHaveBeenCalledTimes(1);
fsMock.readFileSync = originalMock;
});

test('calls prettier.resolveConfig.sync with the file path', () => {
test('calls prettier.resolveConfig.sync with the file path', async () => {
const filePath = require.resolve('../../tests/fixtures/paths/foo.js');
format({
await format({
filePath,
text: defaultInputText(),
eslintConfig: getESLintConfigWithDefaultRules()
Expand All @@ -399,55 +399,56 @@ test('does not raise an error if prettier.resolveConfig.sync is not defined', ()
prettierMock.resolveConfig.sync = originalPrettierMockResolveConfigSync;
});

test('does not raise an error if prettier.resolveConfig is not defined', () => {
test('does not raise an error if prettier.resolveConfig is not defined', async () => {
const filePath = require.resolve('../../tests/fixtures/paths/foo.js');
const originalPrettierMockResolveConfig = prettierMock.resolveConfig;
prettierMock.resolveConfig = undefined;

function callingFormat() {
async function callingFormat() {
return format({
filePath,
text: defaultInputText(),
eslintConfig: getESLintConfigWithDefaultRules()
});
}

expect(callingFormat).not.toThrowError();
await expect(callingFormat).not.toThrowError();

prettierMock.resolveConfig = originalPrettierMockResolveConfig;
});

test('logs if there is a problem making the CLIEngine', () => {
test('logs if there is a problem making the CLIEngine', async () => {
const error = new Error('fake error');
eslintMock.CLIEngine.mockImplementation(() => {
eslintMock.ESLint.mockImplementation(() => {
throw error;
});
expect(() => format({ text: '' })).toThrowError(error);
eslintMock.CLIEngine.mockReset();
await expect(() => format({ text: '' })).rejects.toThrowError(error);
eslintMock.ESLint.mockReset();
expect(logger.error).toHaveBeenCalledTimes(1);
});

function getESLintConfigWithDefaultRules(overrides) {
return {
parserOptions: { ecmaVersion: 7 },
rules: {
semi: [2, 'never'],
'max-len': [2, 120, 2],
indent: [2, 2, { SwitchCase: 1 }],
quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }],
'comma-dangle': [
2,
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'always-multiline'
}
],
'arrow-parens': [2, 'as-needed'],
...overrides
}

parserOptions: { ecmaVersion: 7 },
rules: {
semi: [2, 'never'],
'max-len': [2, 120, 2],
indent: [2, 2, { SwitchCase: 1 }],
quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }],
'comma-dangle': [
2,
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'always-multiline'
}
],
'arrow-parens': [2, 'as-needed'],
...overrides
}
};
}

Expand Down
Loading