Skip to content

Commit

Permalink
feat: add prettier format for eslint config
Browse files Browse the repository at this point in the history
  • Loading branch information
allohamora committed Jan 30, 2025
1 parent 666416c commit 47062f5
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 91 deletions.
60 changes: 22 additions & 38 deletions __tests__/categories/js/eslint/eslint.entrypoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as fs from 'src/utils/fs';
import * as npm from 'src/utils/npm';
import * as mutation from 'src/utils/mutation';
import * as config from 'src/categories/js/eslint/eslint.config';
import * as javascript from 'src/utils/javascript';
import { eslint } from 'src/categories/js/eslint/eslint.entrypoint';
import { createConfig } from './eslint-test.utils';
import { Config } from 'src/categories/js/eslint/config/config.interface';
Expand All @@ -18,6 +19,14 @@ const mutationMocked = jest.mocked(mutation);
jest.mock('src/categories/js/eslint/eslint.config');
const configMocked = jest.mocked(config);

// we need to mock prettier because it doesn't work with jest
// TypeError: A dynamic import callback was invoked without --experimental-vm-modules
// https://github.com/prettier/prettier/issues/15769
jest.mock('src/utils/javascript', () => ({
format: jest.fn().mockImplementation(async (config) => config),
}));
const javascriptMocked = jest.mocked(javascript);

beforeEach(() => {
jest.clearAllMocks();
});
Expand Down Expand Up @@ -57,15 +66,12 @@ describe('eslint', () => {
configMocked.getConfig.mockReturnValueOnce(config);

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

await eslint();

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

Expand All @@ -74,12 +80,12 @@ describe('eslint', () => {
configMocked.getConfig.mockReturnValueOnce(config);

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

await eslint();

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

Expand All @@ -94,16 +100,12 @@ describe('eslint', () => {
configMocked.getConfig.mockReturnValueOnce(config);

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

await eslint();

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

Expand All @@ -114,14 +116,14 @@ describe('eslint', () => {
configMocked.getConfig.mockReturnValueOnce(config);

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

await eslint();

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

Expand Down Expand Up @@ -149,32 +151,14 @@ describe('eslint', () => {
configMocked.getConfig.mockReturnValueOnce(config);

const configFile = `// @ts-check
import globals from "globals";\n
import globals from "globals";
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'
},
}
{files: ["src/**/*.ts"],ignores: ["node_modules"],languageOptions: {globals: {...globals.window},parserOptions: {"ecmaVersion":2020}},plugins: {'@typescript-eslint': eslintPluginTs},rules: {"no-console":"warn"}}
);`;

await eslint();

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

Expand Down
4 changes: 4 additions & 0 deletions __tests__/setup-after-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// we need to mock prettier in all tests because it doesn't work with jest
// TypeError: A dynamic import callback was invoked without --experimental-vm-modules
// https://github.com/prettier/prettier/issues/15769
jest.mock('prettier', () => jest.fn());
31 changes: 31 additions & 0 deletions __tests__/utils/javascript.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import prettier from 'prettier';
import { getConfig } from 'src/categories/js/prettier/prettier.config';
import { defaultConfig } from 'src/categories/js/prettier/config/default.config';
import { format } from 'src/utils/javascript';

// we need to mock prettier because it doesn't work with jest
// TypeError: A dynamic import callback was invoked without --experimental-vm-modules
// https://github.com/prettier/prettier/issues/15769
jest.mock('prettier', () => ({
format: jest.fn(),
}));
const prettierMocked = jest.mocked(prettier);

jest.mock('src/categories/js/prettier/prettier.config', () => ({ getConfig: jest.fn() }));
const getConfigMocked = jest.mocked(getConfig);

describe('format', () => {
test('formats input with default config', async () => {
const input = `const foo = "bar"`;
const formatted = `const foo = 'bar';`;

getConfigMocked.mockImplementation(() => ({ config: { printWidth: 90 } }) as typeof defaultConfig);
prettierMocked.format.mockResolvedValueOnce(formatted);

const actual = await format(input);
const expected = formatted;

expect(actual).toBe(expected);
expect(prettierMocked.format).toHaveBeenCalledWith(input, { parser: 'typescript', printWidth: 90 });
});
});
3 changes: 2 additions & 1 deletion jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleDirectories: ['<rootDir>', 'node_modules'],
testRegex: '.*\.(spec|test)\.ts$',
testRegex: '.*.(spec|test).ts$',
collectCoverageFrom: ['src/**/*.ts'],
passWithNoTests: true,
setupFilesAfterEnv: ['./__tests__/setup-after-env.ts'],
};
8 changes: 3 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
"husky": "^9.1.6",
"jest": "^29.7.0",
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"rollup": "^3.29.5",
"rollup-plugin-typescript2": "^0.36.0",
Expand All @@ -83,7 +82,8 @@
},
"dependencies": {
"inquirer": "8.2.4",
"ora": "5.4.1"
"ora": "5.4.1",
"prettier": "^3.3.3"
},
"lint-staged": {
"*.ts": [
Expand Down
85 changes: 40 additions & 45 deletions src/categories/js/eslint/eslint.entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,50 @@ import { addScripts, installDevelopmentDependencies } from 'src/utils/npm';
import { CONFIG_FILE_NAME, PACKAGE_NAME } from './eslint.const';
import { applyMutations } from 'src/utils/mutation';
import { Config } from './config/config.interface';
import { format } from 'src/utils/javascript';

const buildConfig = (config: Config) => {
const start = config.typescript ? `// @ts-check\n` : '';
const optional = <T>(value: T | undefined, map: (value: T) => string) => (value ? map(value) : '');

export const buildConfig = (config: Config) => {
const start = optional(config.typescript, () => '// @ts-check');

const imports = `${config.imports.map((item) => `${item};`).join('\n')}\n`.trim();

const exportStart = config.typescript ? `export default tseslint.config(` : `export default [`;
const exportEnd = config.typescript ? `);` : `];`;

const imports = `${start}${config.imports.join(';\n')}${config.imports.length ? ';\n' : ''}`;
const configs = config.configs.map((item) => ` ${item},`).join('\n');

const files = config.eslintConfig.files
? ` files: ${JSON.stringify(config.eslintConfig.files).replace(/"/gim, `'`)},`
: '';
const ignores = config.eslintConfig.ignores
? ` ignores: [\n${config.eslintConfig.ignores.map((item) => ` '${item}'`).join(',\n')}\n ],`
: '';

const globals = config.eslintConfig.languageOptions?.globals
? config.eslintConfig.languageOptions.globals.map((item) => ` ...globals.${item}`).join(',\n')
: '';
const parserOptions = config.eslintConfig.languageOptions?.parserOptions
? ` parserOptions: {\n${Object.entries(config.eslintConfig.languageOptions.parserOptions)
.map(([key, value]) => ` ${key}: ${value}`)
.join(',\n')}\n }`
: '';

const plugins = config.eslintConfig.plugins
? ` plugins: { ${Object.entries(config.eslintConfig.plugins)
.map(([key, value]) => `\n '${key}': ${value}`)
.join(',')}\n },`
: '';
const rules = config.eslintConfig.rules
? ` rules: ${JSON.stringify(config.eslintConfig.rules, null, 2)
.replace(/"/gim, "'")
.replace(/\n {2}/gim, '\n' + ' '.repeat(6))
.replace('\n}', '\n }')},`
: '';

const languageOptions = config.eslintConfig.languageOptions
? ` languageOptions: {
globals: {
${globals}
}${parserOptions ? `,\n${parserOptions}` : ''}
},`
: '';

const mainConfig = ` {\n${[files, ignores, languageOptions, plugins, rules].filter(Boolean).join('\n')}\n }`;

return [imports, exportStart, configs, mainConfig, exportEnd].filter(Boolean).join('\n');
const configs = config.configs.map((item) => `${item},`).join('\n');

const files = optional(config.eslintConfig.files, (value) => `files: ${JSON.stringify(value)}`);
const ignores = optional(config.eslintConfig.ignores, (value) => `ignores: ${JSON.stringify(value)}`);

const plugins = optional(
config.eslintConfig.plugins,
(values) =>
`plugins: {${Object.entries(values)
.map(([key, value]) => `'${key}': ${value}`)
.join(',')}}`,
);

const rules = optional(config.eslintConfig.rules, (value) => `rules: ${JSON.stringify(value)}`);

const globals = optional(
config.eslintConfig.languageOptions?.globals,
(value) => `globals: {${value.map((item) => `...globals.${item}`).join(',')}}`,
);
const parserOptions = optional(
config.eslintConfig.languageOptions?.parserOptions,
(value) => `parserOptions: ${JSON.stringify(value)}`,
);

const languageOptions = optional(
config.eslintConfig.languageOptions,
() => `languageOptions: {${[globals, parserOptions].filter(Boolean).join(',')}}`,
);

const mainConfig = `{${[files, ignores, languageOptions, plugins, rules].filter(Boolean).join(',')}}`;

return [start, imports, exportStart, configs, mainConfig, exportEnd].filter(Boolean).join('\n');
};

export const eslint = async () => {
Expand All @@ -63,7 +58,7 @@ export const eslint = async () => {

await installDevelopmentDependencies(PACKAGE_NAME, ...dependencies);

const eslintConfig = buildConfig(config);
const eslintConfig = await format(buildConfig(config));

await addFileToRoot(CONFIG_FILE_NAME, eslintConfig);
await addScripts(...scripts);
Expand Down
8 changes: 8 additions & 0 deletions src/utils/javascript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import prettier from 'prettier';
import { getConfig } from 'src/categories/js/prettier/prettier.config';

export const format = async (input: string) => {
const { config } = getConfig();

return await prettier.format(input, { parser: 'typescript', ...config } as prettier.Options);
};

0 comments on commit 47062f5

Please sign in to comment.