Skip to content

Commit

Permalink
feat(linter): add generator for converting to flat config
Browse files Browse the repository at this point in the history
  • Loading branch information
meeroslav committed Jul 18, 2023
1 parent b07d24e commit 2344d97
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 10 deletions.
8 changes: 8 additions & 0 deletions docs/generated/manifests/menus.json
Original file line number Diff line number Diff line change
Expand Up @@ -5405,6 +5405,14 @@
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{
"id": "convert-to-flat-config",
"path": "/packages/linter/generators/convert-to-flat-config",
"name": "convert-to-flat-config",
"children": [],
"isExternal": false,
"disableCollapsible": false
}
],
"isExternal": false,
Expand Down
9 changes: 9 additions & 0 deletions docs/generated/manifests/packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,15 @@
"originalFilePath": "/packages/linter/src/generators/workspace-rule/schema.json",
"path": "/packages/linter/generators/workspace-rule",
"type": "generator"
},
"/packages/linter/generators/convert-to-flat-config": {
"description": "Convert an Nx workspace to a Flat ESLint config.",
"file": "generated/packages/linter/generators/convert-to-flat-config.json",
"hidden": false,
"name": "convert-to-flat-config",
"originalFilePath": "/packages/linter/src/generators/convert-to-flat-config/schema.json",
"path": "/packages/linter/generators/convert-to-flat-config",
"type": "generator"
}
},
"path": "/packages/linter"
Expand Down
9 changes: 9 additions & 0 deletions docs/generated/packages-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,15 @@
"originalFilePath": "/packages/linter/src/generators/workspace-rule/schema.json",
"path": "linter/generators/workspace-rule",
"type": "generator"
},
{
"description": "Convert an Nx workspace to a Flat ESLint config.",
"file": "generated/packages/linter/generators/convert-to-flat-config.json",
"hidden": false,
"name": "convert-to-flat-config",
"originalFilePath": "/packages/linter/src/generators/convert-to-flat-config/schema.json",
"path": "linter/generators/convert-to-flat-config",
"type": "generator"
}
],
"githubRoot": "https://github.com/nrwl/nx/blob/master",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "convert-to-flat-config",
"factory": "./src/generators/convert-to-flat-config/generator",
"schema": {
"$schema": "http://json-schema.org/schema",
"$id": "ConvertToFlatConfig",
"cli": "nx",
"description": "Convert an Nx workspace to a Flat ESLint config.",
"type": "object",
"properties": {
"skipFormat": {
"type": "boolean",
"description": "Skip formatting files.",
"default": false,
"x-priority": "internal"
}
},
"additionalProperties": false,
"required": [],
"presets": []
},
"description": "Convert an Nx workspace to a Flat ESLint config.",
"implementation": "/packages/linter/src/generators/convert-to-flat-config/generator.ts",
"aliases": [],
"hidden": false,
"path": "/packages/linter/src/generators/convert-to-flat-config/schema.json",
"type": "generator"
}
1 change: 1 addition & 0 deletions docs/shared/reference/sitemap.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@
- [generators](/packages/linter/generators)
- [workspace-rules-project](/packages/linter/generators/workspace-rules-project)
- [workspace-rule](/packages/linter/generators/workspace-rule)
- [convert-to-flat-config](/packages/linter/generators/convert-to-flat-config)
- [nest](/packages/nest)
- [documents](/packages/nest/documents)
- [Overview](/packages/nest/documents/overview)
Expand Down
5 changes: 5 additions & 0 deletions packages/linter/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
"factory": "./src/generators/workspace-rule/workspace-rule#lintWorkspaceRuleGenerator",
"schema": "./src/generators/workspace-rule/schema.json",
"description": "Create a new Workspace ESLint rule."
},
"convert-to-flat-config": {
"factory": "./src/generators/convert-to-flat-config/generator",
"schema": "./src/generators/convert-to-flat-config/schema.json",
"description": "Convert an Nx workspace to a Flat ESLint config."
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Tree, readProjectConfiguration } from '@nx/devkit';

import { convertToFlatConfigGenerator } from './generator';
import { ConvertToFlatConfigGeneratorSchema } from './schema';

describe('convert-to-flat-config generator', () => {
let tree: Tree;
const options: ConvertToFlatConfigGeneratorSchema = {};

beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});

it('should run successfully', async () => {
// await convertToFlatConfigGenerator(tree, options);
// const config = readProjectConfiguration(tree, 'test');
// expect(config).toBeDefined();
expect(true).toBeTruthy();
});
});
207 changes: 207 additions & 0 deletions packages/linter/src/generators/convert-to-flat-config/generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import {
formatFiles,
getProjects,
ProjectConfiguration,
Tree,
readJson,
updateNxJson,
updateProjectConfiguration,
names,
} from '@nx/devkit';
import { ConvertToFlatConfigGeneratorSchema } from './schema';
import { findEslintFile } from '../utils/eslint-file';
import { join } from 'path';
import { ESLint } from 'eslint';

export async function convertToFlatConfigGenerator(
tree: Tree,
options: ConvertToFlatConfigGeneratorSchema
) {
const eslintFile = findEslintFile(tree);
if (!eslintFile) {
throw new Error('Could not find root eslint file');
}
if (!eslintFile.endsWith('.json')) {
throw new Error(
'Only json eslint config files are supported for conversion'
);
}

// rename root eslint config to eslint.config.js
convertRootToFlatConfig(tree);
// rename and map files
const projects = getProjects(tree);
for (const [project, projectConfig] of projects) {
convertProjectToFlatConfig(tree, project, projectConfig);
}
// replace references in nx.json
updateNxJsonConfig(tree);
// install missing packages

if (!options.skipFormat) {
await formatFiles(tree);
}
}

export default convertToFlatConfigGenerator;

function convertRootToFlatConfig(tree: Tree) {
if (tree.exists('.eslintrc.base.json')) {
mapEslintJsonToFlatConfig(
tree,
'',
'.eslintrc.base.json',
'eslint.config.base.js'
);
}
if (tree.exists('.eslintrc.json')) {
mapEslintJsonToFlatConfig(tree, '', '.eslintrc.json', 'eslint.config.js');
}
}

function convertProjectToFlatConfig(
tree: Tree,
project: string,
projectConfig: ProjectConfiguration
) {
if (tree.exists(`${projectConfig.root}/.eslintrc.json`)) {
if (projectConfig.targets) {
const eslintTargets = Object.keys(projectConfig.targets).filter(
(t) => projectConfig.targets[t].executor === '@nrwl/linter:eslint'
);
for (const target of eslintTargets) {
projectConfig.targets[target].options = {
...projectConfig.targets[target].options,
eslintConfig: `${projectConfig.root}/eslint.config.js`,
};
}
updateProjectConfiguration(tree, project, projectConfig);
}

mapEslintJsonToFlatConfig(
tree,
projectConfig.root,
'.eslintrc.json',
'eslint.config.js'
);
}
}

function mapEslintJsonToFlatConfig(
tree: Tree,
root: string,
source: string,
destination: string
) {
const config: ESLint.ConfigData = readJson(tree, `${root}/${source}`);

const extendsConfig = config.extends
? Array.isArray(config.extends)
? config.extends
: [config.extends]
: [];
const baseExtends = extendsConfig
.filter((e) => e.startsWith('.'))
.map((e, index) => ({
imp: `const baseConfig${index ?? ''} = require('${e}');`,
config: `...baseConfig${index ?? ''},`,
}));
// tODO map plugins to classname imports
const newConfig = `
${baseExtends
.map((b) => `${b.imp}\n`)
.join()}import { FlatCompat } from "@eslint/eslintrc";
const compat = new FlatCompat({
baseDirectory: __dirname,
});
export default [
${baseExtends.map((b) => `${b.config}\n`).join()}${mapExtends(
extendsConfig
)}${mapIgnores(config)}${mapESLintIgnores(
tree,
root
)}${mapPluginsRulesAndSettings(config)}${mapOverrides(config)}
];
`;

tree.delete(join(root, source));
tree.delete(join(root, '.eslintignore'));
tree.write(join(root, destination), newConfig);
}

function mapExtends(extendsConfig: string[]): string {
const pluginExtends = extendsConfig.filter((e) => e.startsWith('plugin:'));
if (pluginExtends.length === 0) {
return '';
}
return `...compat.extends(${pluginExtends
.map((e) => `'${e}'`)
.join(', ')}),\n`;
}

function mapIgnores(config: ESLint.ConfigData): string {
if (!config.ignorePatterns) {
return '';
}
return `{
ignores: [${config.ignorePatterns}]
},\n`;
}

function mapESLintIgnores(tree: Tree, root: string): string {
if (!tree.exists(`${root}/.eslintignore`)) {
return '';
}
const ignores = tree
.read(`${root}/.eslintignore`, 'utf-8')
.split('\n')
.map((i) => `'${i}'`)
.join(', ');
return `{
ignores: [${ignores}]
},\n`;
}

function mapPluginsRulesAndSettings(config: ESLint.ConfigData): string {
if (!config.plugins) {
return '';
}
let result = '';
if (config.plugins) {
result += `plugins: {\n${config.plugins
.map((p) => `'${p}': ${names(p).className},\n'`)
.join()}\n},\n`;
}
if (config.rules) {
result += `rules: {\n${Object.keys(config.rules)
.map((r) => `${r}: ${JSON.stringify(config.rules[r])},\n`)
.join()}\n},\n`;
}
if (config.settings) {
result += `settings: {\n${Object.keys(config.settings)
.map((s) => `${s}: ${JSON.stringify(config.settings[s])},\n`)
.join()}\n},\n`;
}
return `{\n${result}\n},\n`;
}

function mapOverrides(config: ESLint.ConfigData): string {
if (!config.overrides) {
return '';
}
// TODO map parsers and parserOptions
return config.overrides.map((o) => `${JSON.stringify(o)},\n`).join();
}

function updateNxJsonConfig(tree) {
if (tree.exists('nx.json')) {
const content = tree.read('nx.json', 'utf-8');
const strippedConfig: string = content
.replace('.eslintrc.json', 'eslint.config.js')
.replace('.eslintrc.base.json', 'eslint.config.base.js')
.replace(/".*\.eslintignore.json",?/, '');
updateNxJson(tree, JSON.parse(strippedConfig));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface ConvertToFlatConfigGeneratorSchema {
skipFormat?: boolean;
}
17 changes: 17 additions & 0 deletions packages/linter/src/generators/convert-to-flat-config/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "ConvertToFlatConfig",
"cli": "nx",
"description": "Convert an Nx workspace to a Flat ESLint config.",
"type": "object",
"properties": {
"skipFormat": {
"type": "boolean",
"description": "Skip formatting files.",
"default": false,
"x-priority": "internal"
}
},
"additionalProperties": false,
"required": []
}
Loading

0 comments on commit 2344d97

Please sign in to comment.