Skip to content

Commit

Permalink
fix(linter): fix workspace-rule naming with flat config (#20782)
Browse files Browse the repository at this point in the history
  • Loading branch information
meeroslav authored and jaysoo committed Dec 18, 2023
1 parent fa4cfb2 commit 5c87bc1
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 13 deletions.
2 changes: 1 addition & 1 deletion e2e/eslint/src/linter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ describe('Linter', () => {
);
updateFile(newRulePath, updatedRuleContents);

const newRuleNameForUsage = `@nx/workspace/${newRuleName}`;
const newRuleNameForUsage = `@nx/workspace-${newRuleName}`;

// Add the new workspace rule to the lint config and run linting
const eslintrc = readJson('.eslintrc.json');
Expand Down
6 changes: 6 additions & 0 deletions packages/eslint-plugin/migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
"version": "16.0.0-beta.1",
"description": "Replace @nrwl/eslint-plugin-nx with @nx/eslint-plugin",
"implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages"
},
"update-17-2-6-rename-workspace-rules": {
"cli": "nx",
"version": "17-2-6-beta.1",
"description": "Rename workspace rules from @nx/workspace/name to @nx/workspace-name",
"implementation": "./src/migrations/update-17-2-6-rename-workspace-rules/rename-workspace-rules"
}
},
"packageJsonUpdates": {},
Expand Down
8 changes: 5 additions & 3 deletions packages/eslint-plugin/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { workspaceRoot } from '@nx/devkit';
import { join } from 'path';

export const WORKSPACE_PLUGIN_DIR = join(workspaceRoot, 'tools/eslint-rules');
export const WORKSPACE_RULES_PATH = 'tools/eslint-rules';

export const WORKSPACE_PLUGIN_DIR = join(workspaceRoot, WORKSPACE_RULES_PATH);

/**
* We add a namespace so that we mitigate the risk of rule name collisions as much as
Expand All @@ -11,6 +13,6 @@ export const WORKSPACE_PLUGIN_DIR = join(workspaceRoot, 'tools/eslint-rules');
* E.g. if a user writes a rule called "foo", then they will include it in their ESLint
* config files as:
*
* "@nx/workspace/foo": "error"
* "@nx/workspace-foo": "error"
*/
export const WORKSPACE_RULE_NAMESPACE = 'workspace';
export const WORKSPACE_RULE_PREFIX = 'workspace';
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Tree, readJson, writeJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports';
import { WORKSPACE_PLUGIN_DIR } from '../../constants';
import update from './rename-workspace-rules';

const rule1Name = 'test-rule';
const rule2Name = 'my-rule';

describe('update-17-2-6-rename-workspace-rules', () => {
let tree: Tree;

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

const { lintWorkspaceRuleGenerator } = require('@nx/' +
'eslint/src/generators/workspace-rule/workspace-rule');

lintWorkspaceRuleGenerator(tree, {
name: rule1Name,
});
lintWorkspaceRuleGenerator(tree, {
name: rule2Name,
});

jest.mock(WORKSPACE_PLUGIN_DIR, () => ({
rules: {
[rule1Name]: {},
[rule2Name]: {},
},
}));
});

it('should replace rules in config files', async () => {
writeJson(tree, '.eslintrc.json', {
plugins: ['@nx'],
rules: {
[`@nx/workspace/${rule1Name}`]: 'error',
[`@nx/workspace/${rule2Name}`]: 'error',
},
});

update(tree);

expect(Object.keys(readJson(tree, '.eslintrc.json').rules)).toEqual([
'@nx/workspace-test-rule',
'@nx/workspace-my-rule',
]);
});

it('should replace rules in random js files', async () => {
tree.write(
'custom.js',
`
export default {
plugins: ['@nx'],
rules: {
"@nx/workspace/${rule1Name}": 'error',
"@nx/workspace/${rule2Name}": 'error',
},
}
`
);

update(tree);

expect(tree.read('custom.js', 'utf-8')).toContain(
`@nx/workspace-test-rule`
);
expect(tree.read('custom.js', 'utf-8')).toContain(`@nx/workspace-my-rule`);
expect(tree.read('custom.js', 'utf-8')).not.toContain(
`@nx/workspace/test-rule`
);
expect(tree.read('custom.js', 'utf-8')).not.toContain(
`@nx/workspace/my-rule`
);
});

it('should replace rules in comments', async () => {
tree.write(
'custom.js',
`import { getSourceNodes } from '@nx/workspace/src/utilities/typescript';
// eslint-disable-next-line @nx/workspace/${rule1Name}
import { something } from 'somewhere';
/* eslint-disable @nx/workspace/${rule2Name} */
// something that should remain the same @nx/workspace/unknown-rule
/* eslint-enable @nx/workspace/${rule2Name} */
`
);

update(tree);

expect(tree.read('custom.js', 'utf-8')).toMatchInlineSnapshot(`
"import { getSourceNodes } from '@nx/workspace/src/utilities/typescript';
// eslint-disable-next-line @nx/workspace-test-rule
import { something } from 'somewhere';
/* eslint-disable @nx/workspace-my-rule */
// something that should remain the same @nx/workspace/unknown-rule
/* eslint-enable @nx/workspace-my-rule */
"
`);
});

it('should not replace unknown rules', async () => {
tree.write(
'custom.js',
`
export default {
plugins: ['@nx'],
rules: {
"@nx/workspace/random-rule": 'error',
},
}
`
);

update(tree);

expect(tree.read('custom.js', 'utf-8')).not.toContain(
`@nx/workspace-random-rule`
);
expect(tree.read('custom.js', 'utf-8')).toContain(
`@nx/workspace/random-rule`
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Tree, formatFiles, visitNotIgnoredFiles } from '@nx/devkit';
import { isBinaryPath } from '@nx/devkit/src/utils/binary-extensions';
import { WORKSPACE_PLUGIN_DIR, WORKSPACE_RULES_PATH } from '../../constants';

export default async function renameWorkspaceRule(tree: Tree): Promise<void> {
if (!tree.exists(WORKSPACE_RULES_PATH)) {
return;
}
let ruleNames: string[] = [];
try {
ruleNames = Object.keys(require(WORKSPACE_PLUGIN_DIR).rules);
} catch (e) {
return;
}

visitNotIgnoredFiles(tree, '.', (path) => {
if (isBinaryPath(path)) {
return;
}

let contents = tree.read(path, 'utf-8') as string;
ruleNames.forEach((ruleName) => {
contents = contents.replace(
new RegExp(`@nx/workspace/${ruleName}`, 'g'),
`@nx/workspace-${ruleName}`
);
});
tree.write(path, contents);
});

await formatFiles(tree);
}
6 changes: 4 additions & 2 deletions packages/eslint-plugin/src/resolve-workspace-rules.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { TSESLint } from '@typescript-eslint/utils';
import { existsSync } from 'fs';
import { registerTsProject } from '@nx/js/src/internal';
import { WORKSPACE_PLUGIN_DIR, WORKSPACE_RULE_NAMESPACE } from './constants';
import { WORKSPACE_PLUGIN_DIR, WORKSPACE_RULE_PREFIX } from './constants';
import { join } from 'path';

type ESLintRules = Record<string, TSESLint.RuleModule<string, unknown[]>>;
Expand All @@ -25,7 +25,9 @@ export const workspaceRules = ((): ESLintRules => {
// Apply the namespace to the resolved rules
const namespacedRules: ESLintRules = {};
for (const [ruleName, ruleConfig] of Object.entries(rules as ESLintRules)) {
namespacedRules[`${WORKSPACE_RULE_NAMESPACE}/${ruleName}`] = ruleConfig;
namespacedRules[`${WORKSPACE_RULE_PREFIX}-${ruleName}`] = ruleConfig;
// keep the old namespaced rules for backwards compatibility
namespacedRules[`${WORKSPACE_RULE_PREFIX}/${ruleName}`] = ruleConfig;
}
return namespacedRules;
} catch (err) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ exports[`@nx/eslint:workspace-rule --dir should support creating the rule in a n
import { ESLintUtils } from '@typescript-eslint/utils';
// NOTE: The rule will be available in ESLint configs as "@nx/workspace/another-rule"
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-another-rule"
export const RULE_NAME = 'another-rule';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
Expand Down Expand Up @@ -75,7 +75,7 @@ exports[`@nx/eslint:workspace-rule --dir should support creating the rule in a n
import { ESLintUtils } from '@typescript-eslint/utils';
// NOTE: The rule will be available in ESLint configs as "@nx/workspace/one-more-rule"
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-one-more-rule"
export const RULE_NAME = 'one-more-rule';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
Expand Down Expand Up @@ -131,7 +131,7 @@ exports[`@nx/eslint:workspace-rule should generate the required files 1`] = `
import { ESLintUtils } from '@typescript-eslint/utils';
// NOTE: The rule will be available in ESLint configs as "@nx/workspace/my-rule"
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-my-rule"
export const RULE_NAME = 'my-rule';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { ESLintUtils } from '@typescript-eslint/utils';

// NOTE: The rule will be available in ESLint configs as "@nx/workspace/<%= name %>"
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-<%= name %>"
export const RULE_NAME = '<%= name %>';

export const rule = ESLintUtils.RuleCreator(() => __filename)({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export async function lintWorkspaceRuleGenerator(
logger.info(`NX Reminder: Once you have finished writing your rule logic, you need to actually enable the rule within an appropriate ESLint config in your workspace, for example:
"rules": {
"@nx/workspace/${options.name}": "error"
"@nx/workspace-${options.name}": "error"
}
`);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ exports[`update-typescript-eslint migration should update the rules 1`] = `
import { ESLintUtils } from '@typescript-eslint/utils';
// NOTE: The rule will be available in ESLint configs as "@nx/workspace/lint-rule"
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-lint-rule"
export const RULE_NAME = 'lint-rule';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default {
import { ESLintUtils } from '@typescript-eslint/utils';
// NOTE: The rule will be available in ESLint configs as "@nx/workspace/lint-rule"
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-lint-rule"
export const RULE_NAME = 'lint-rule';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
Expand Down

0 comments on commit 5c87bc1

Please sign in to comment.