Skip to content

Commit

Permalink
fix(misc): handle flat pre-defined flat configs when migrating to mon…
Browse files Browse the repository at this point in the history
…orepo-style
  • Loading branch information
jaysoo committed Sep 10, 2024
1 parent 09b2d8d commit 00130f5
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 0 deletions.
6 changes: 6 additions & 0 deletions packages/eslint/src/generators/init/init-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
generateSpreadElement,
removeCompatExtends,
removePlugin,
removePredefinedConfigs,
} from '../utils/flat-config/ast-utils';
import { hasEslintPlugin } from '../utils/plugin';
import { ESLINT_CONFIG_FILENAMES } from '../../utils/config-file';
Expand Down Expand Up @@ -152,6 +153,11 @@ function migrateEslintFile(projectEslintPath: string, tree: Tree) {
'plugin:@nrwl/typescript',
'plugin:@nrwl/javascript',
]);
config = removePredefinedConfigs(config, '@nx/eslint-plugin', 'nx', [
'flat/base',
'flat/typescript',
'flat/javascript',
]);
tree.write(projectEslintPath, config);
} else {
updateJson(tree, projectEslintPath, (json) => {
Expand Down
89 changes: 89 additions & 0 deletions packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import {
generateFlatOverride,
generatePluginExtendsElementWithCompatFixup,
removeCompatExtends,
removeImportFromFlatConfig,
removeOverridesFromLintConfig,
removePlugin,
removePredefinedConfigs,
replaceOverride,
} from './ast-utils';
import { stripIndents } from '@nx/devkit';

describe('ast-utils', () => {
const printer = ts.createPrinter();
Expand Down Expand Up @@ -341,6 +344,32 @@ describe('ast-utils', () => {
});
});

describe('removeImportFromFlatConfig', () => {
it('should remove existing import from config if the var name matches', () => {
const content = stripIndents`
const nx = require("@nx/eslint-plugin");
const thisShouldRemain = require("@nx/eslint-plugin");
const playwright = require('eslint-plugin-playwright');
module.exports = [
playwright.configs['flat/recommended'],
];
`;
const result = removeImportFromFlatConfig(
content,
'nx',
'@nx/eslint-plugin'
);
expect(result).toMatchInlineSnapshot(`
"
const thisShouldRemain = require("@nx/eslint-plugin");
const playwright = require('eslint-plugin-playwright');
module.exports = [
playwright.configs['flat/recommended'],
];"
`);
});
});

describe('addCompatToFlatConfig', () => {
it('should add compat to config', () => {
const content = `const baseConfig = require("../../eslint.config.js");
Expand Down Expand Up @@ -966,6 +995,66 @@ describe('ast-utils', () => {
});
});

describe('removePredefinedConfigs', () => {
it('should remove config objects and import', () => {
const content = stripIndents`
const nx = require("@nx/eslint-plugin");
const playwright = require('eslint-plugin-playwright');
module.exports = [
...nx.config['flat/base'],
...nx.config['flat/typescript'],
...nx.config['flat/javascript'],
playwright.configs['flat/recommended'],
];
`;

const result = removePredefinedConfigs(
content,
'@nx/eslint-plugin',
'nx',
['flat/base', 'flat/typescript', 'flat/javascript']
);

expect(result).toMatchInlineSnapshot(`
"
const playwright = require('eslint-plugin-playwright');
module.exports = [
playwright.configs['flat/recommended'],
];"
`);
});

it('should keep configs that are not in the list', () => {
const content = stripIndents`
const nx = require("@nx/eslint-plugin");
const playwright = require('eslint-plugin-playwright');
module.exports = [
...nx.config['flat/base'],
...nx.config['flat/typescript'],
...nx.config['flat/javascript'],
...nx.config['flat/react'],
playwright.configs['flat/recommended'],
];
`;

const result = removePredefinedConfigs(
content,
'@nx/eslint-plugin',
'nx',
['flat/base', 'flat/typescript', 'flat/javascript']
);

expect(result).toMatchInlineSnapshot(`
"const nx = require("@nx/eslint-plugin");
const playwright = require('eslint-plugin-playwright');
module.exports = [
...nx.config['flat/react'],
playwright.configs['flat/recommended'],
];"
`);
});
});

describe('generatePluginExtendsElementWithCompatFixup', () => {
it('should return spread element with fixupConfigRules call wrapping the extended plugin', () => {
const result = generatePluginExtendsElementWithCompatFixup('my-plugin');
Expand Down
90 changes: 90 additions & 0 deletions packages/eslint/src/generators/utils/flat-config/ast-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,50 @@ export function addImportToFlatConfig(
]);
}

/**
* Remove an import from flat config
*/
export function removeImportFromFlatConfig(
content: string,
variable: string,
imp: string
): string {
const source = ts.createSourceFile(
'',
content,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.JS
);

const changes: StringChange[] = [];

ts.forEachChild(source, (node) => {
// we can only combine object binding patterns
if (
ts.isVariableStatement(node) &&
ts.isVariableDeclaration(node.declarationList.declarations[0]) &&
ts.isIdentifier(node.declarationList.declarations[0].name) &&
node.declarationList.declarations[0].name.getText() === variable &&
ts.isCallExpression(node.declarationList.declarations[0].initializer) &&
node.declarationList.declarations[0].initializer.expression.getText() ===
'require' &&
ts.isStringLiteral(
node.declarationList.declarations[0].initializer.arguments[0]
) &&
node.declarationList.declarations[0].initializer.arguments[0].text === imp
) {
changes.push({
type: ChangeType.Delete,
start: node.pos,
length: node.end - node.pos,
});
}
});

return applyChangesToString(content, changes);
}

/**
* Injects new ts.expression to the end of the module.exports array.
*/
Expand Down Expand Up @@ -570,6 +614,52 @@ export function removeCompatExtends(
return applyChangesToString(content, changes);
}

export function removePredefinedConfigs(
content: string,
moduleImport: string,
moduleVariable: string,
configs: string[]
): string {
const source = ts.createSourceFile(
'',
content,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.JS
);
const changes: StringChange[] = [];
let removeImport = true;
findAllBlocks(source).forEach((node) => {
if (
ts.isSpreadElement(node) &&
ts.isElementAccessExpression(node.expression) &&
ts.isPropertyAccessExpression(node.expression.expression) &&
ts.isIdentifier(node.expression.expression.expression) &&
node.expression.expression.expression.getText() === moduleVariable &&
ts.isStringLiteral(node.expression.argumentExpression)
) {
const config = node.expression.argumentExpression.getText();
// Check the text without quotes
if (configs.includes(config.substring(1, config.length - 1))) {
changes.push({
type: ChangeType.Delete,
start: node.pos,
length: node.end - node.pos + 1, // trailing comma
});
} else {
// If there is still a config used, do not remove import
removeImport = false;
}
}
});

let updated = applyChangesToString(content, changes);
if (removeImport) {
updated = removeImportFromFlatConfig(updated, moduleVariable, moduleImport);
}
return updated;
}

/**
* Add plugins block to the top of the export blocks
*/
Expand Down

0 comments on commit 00130f5

Please sign in to comment.