Skip to content

Commit

Permalink
Merge pull request #243 from allohamora/feature/eslint-flat-config
Browse files Browse the repository at this point in the history
Feature/eslint flat config
  • Loading branch information
allohamora authored Sep 21, 2024
2 parents 5904be3 + f778f46 commit a7401de
Show file tree
Hide file tree
Showing 18 changed files with 4,814 additions and 3,449 deletions.
3 changes: 0 additions & 3 deletions .eslintignore

This file was deleted.

32 changes: 0 additions & 32 deletions .eslintrc.json

This file was deleted.

22 changes: 22 additions & 0 deletions .ncurc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const red = (log) => `\x1b[31m${log}\x1b[0m`;

module.exports = {
deep: true,
upgrade: true,

/**
* @description https://github.com/raineorshine/npm-check-updates?tab=readme-ov-file#filterresults
* @param {string} packageName
* @param {{ currentVersion: string, upgradedVersion: string }} versions
* @returns {boolean}
*/
filterResults: (packageName, { currentVersion, upgradedVersion }) => {
if (currentVersion.startsWith('^')) {
return true;
}

console.warn(red(`${packageName}@${currentVersion} wasn't upgraded to ${upgradedVersion} because it is static`));

return false;
},
};
6 changes: 4 additions & 2 deletions __tests__/categories/js/eslint/eslint-test.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { Config } from 'src/categories/js/eslint/config/config.interface';

export const createConfig = ({
dependencies = [],
imports = [],
configs = [],
eslintConfig = {},
ignore = [],
typescript = false,
scripts = [],
mutations = [],
}: Partial<Config> = {}) => {
return { dependencies, eslintConfig, ignore, scripts, mutations };
return { dependencies, imports, configs, eslintConfig, typescript, scripts, mutations };
};
120 changes: 114 additions & 6 deletions __tests__/categories/js/eslint/eslint.entrypoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,130 @@ describe('eslint', () => {
expect(npmMocked.installDevelopmentDependencies).toHaveBeenCalledWith('eslint', ...config.dependencies);
});

test('should add .eslintrc.json to root', async () => {
const config = createConfig({ eslintConfig: { extends: ['__test__'] } });
test('should add eslint.config.mjs to root', async () => {
const config = createConfig({ eslintConfig: { ignores: ['__test__'] } });
configMocked.getConfig.mockReturnValueOnce(config);

const configFile = `export default [
{
ignores: [
'__test__'
],
}
];`;

await eslint();

expect(fsMocked.addFileToRoot).toHaveBeenCalledWith('eslint.config.mjs', configFile);
});

test('should handle empty eslintConfig gracefully', async () => {
const config = createConfig({ eslintConfig: {} });
configMocked.getConfig.mockReturnValueOnce(config);

const configFile = `export default [
{\n
}
];`;

await eslint();

expect(fsMocked.addJsonFileToRoot).toHaveBeenCalledWith('.eslintrc.json', config.eslintConfig);
expect(fsMocked.addFileToRoot).toHaveBeenCalledWith('eslint.config.mjs', configFile);
});

test('should add .eslintignore to root', async () => {
const config = createConfig({ ignore: ['node_modules', 'dist'] });
test('should handle empty parserOptions correctly', async () => {
const config = createConfig({
eslintConfig: {
languageOptions: {
parserOptions: undefined,
},
},
});
configMocked.getConfig.mockReturnValueOnce(config);

const configFile = `export default [
{
languageOptions: {
globals: {\n
}
},
}
];`;

await eslint();

expect(fsMocked.addFileToRoot).toHaveBeenCalledWith('eslint.config.mjs', configFile);
});

test('should handle configs mapping correctly', async () => {
const config = createConfig({
configs: ['__test1__', '__test2__'],
});
configMocked.getConfig.mockReturnValueOnce(config);

const configFile = `export default [
__test1__,
__test2__,
{\n
}
];`;

await eslint();

expect(fsMocked.addFileToRoot).toHaveBeenCalledWith('eslint.config.mjs', configFile);
});

test('should handle complex eslintConfig correctly', async () => {
const config = createConfig({
typescript: true,
imports: ['import globals from "globals";'],
eslintConfig: {
files: ['src/**/*.ts'],
ignores: ['node_modules'],
languageOptions: {
globals: ['window'],
parserOptions: {
ecmaVersion: 2020,
},
},
plugins: {
'@typescript-eslint': 'eslintPluginTs',
},
rules: {
'no-console': 'warn',
},
},
});
configMocked.getConfig.mockReturnValueOnce(config);

const configFile = `// @ts-check
import globals from "globals";\n
export default tseslint.config(
{
files: ['src/**/*.ts'],
ignores: [
'node_modules'
],
languageOptions: {
globals: {
...globals.window
},
parserOptions: {
ecmaVersion: 2020
}
},
plugins: {
'@typescript-eslint': eslintPluginTs
},
rules: {
'no-console': 'warn'
},
}
);`;

await eslint();

expect(fsMocked.addFileToRoot).toHaveBeenCalledWith('.eslintignore', 'node_modules\ndist');
expect(fsMocked.addFileToRoot).toHaveBeenCalledWith('eslint.config.mjs', configFile);
});

test('should add npm script to package.json', async () => {
Expand Down
19 changes: 11 additions & 8 deletions __tests__/categories/js/eslint/eslint.utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ describe('prettierMutation', () => {

const expected = createConfig({
dependencies: ['eslint-plugin-prettier', 'eslint-config-prettier'],
eslintConfig: { extends: ['plugin:prettier/recommended'] },
imports: [`import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'`],
configs: ['eslintPluginPrettierRecommended'],
});

expect(actual).toEqual(expected);
Expand All @@ -44,14 +45,16 @@ describe('prettierMutation', () => {

const actual = createConfig({
dependencies: ['__test__'],
eslintConfig: { extends: ['__test__'] },
imports: ['__test__'],
configs: ['__test__'],
});

await prettierMutation(actual);

const expected = createConfig({
dependencies: ['__test__', 'eslint-plugin-prettier', 'eslint-config-prettier'],
eslintConfig: { extends: ['__test__', 'plugin:prettier/recommended'] },
imports: ['__test__', `import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'`],
configs: ['__test__', 'eslintPluginPrettierRecommended'],
});

expect(actual).toEqual(expected);
Expand All @@ -60,8 +63,8 @@ describe('prettierMutation', () => {

describe('jestMutation', () => {
test('should add jest env if jest installed', async () => {
const actual = createConfig({ eslintConfig: { env: {} } });
const expected = createConfig({ eslintConfig: { env: { jest: true } } });
const actual = createConfig({ eslintConfig: { languageOptions: { globals: [] } } });
const expected = createConfig({ eslintConfig: { languageOptions: { globals: ['jest'] } } });

jestUtilsMocked.isJestInstalled.mockResolvedValue(true);

Expand All @@ -72,7 +75,7 @@ describe('jestMutation', () => {

test('should add jest env object if not exists', async () => {
const actual = createConfig();
const expected = createConfig({ eslintConfig: { env: { jest: true } } });
const expected = createConfig({ eslintConfig: { languageOptions: { globals: ['jest'] } } });

jestUtilsMocked.isJestInstalled.mockResolvedValueOnce(true);

Expand All @@ -94,7 +97,7 @@ describe('jestMutation', () => {
});

describe('isEslintInstalled', () => {
test('should call isInstalledAndInRootCheck with eslint and .eslintrc.json', () => {
expect(installedMocked.isInstalledAndInRootCheck).toHaveBeenCalledWith('eslint', '.eslintrc.json');
test('should call isInstalledAndInRootCheck with eslint and eslint.config.mjs', () => {
expect(installedMocked.isInstalledAndInRootCheck).toHaveBeenCalledWith('eslint', 'eslint.config.mjs');
});
});
49 changes: 49 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// @ts-check
import globals from 'globals';
import eslint from '@eslint/js';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import beautifulSort from 'eslint-plugin-beautiful-sort';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import tseslint from 'typescript-eslint';

export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
eslintPluginPrettierRecommended,
{
files: ['**/*.ts'],
ignores: ['node_modules', 'dist', 'bin'],
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
parserOptions: {
project: true,
},
},
plugins: {
'@typescript-eslint': tsPlugin,
'beautiful-sort': beautifulSort,
},
rules: {
'no-use-before-define': 'error',
'object-shorthand': 'warn',
'no-async-promise-executor': 'warn',
'@typescript-eslint/consistent-type-definitions': ['error', 'type'],
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-misused-promises': 'warn',
'@typescript-eslint/no-deprecated': 'error',
'beautiful-sort/import': [
'error',
{
special: [],
order: ['special', 'namespace', 'default', 'defaultObj', 'obj', 'none'],
},
],
},
},
);
Loading

0 comments on commit a7401de

Please sign in to comment.