Skip to content

Commit

Permalink
feat(nx-plugin): add plugin eslint rules (#9697)
Browse files Browse the repository at this point in the history
* feat(nx-plugin): add lint rule for nx-plugin validation

* chore(repo): review feedback

Co-authored-by: Giora Guttsait <giora111@gmail.com>

* chore(repo): review feedback

Co-authored-by: Giora Guttsait <giora111@gmail.com>

* chore(nx-plugin): review comments

* chore(nx-plugin): review feedback

Co-authored-by: Giora Guttsait <giora111@gmail.com>
  • Loading branch information
AgentEnder and gioragutt committed Jun 3, 2022
1 parent 70efd2e commit 10363e3
Show file tree
Hide file tree
Showing 30 changed files with 1,230 additions and 129 deletions.
8 changes: 7 additions & 1 deletion docs/generated/devkit/index.md
Expand Up @@ -434,7 +434,13 @@ A plugin for Nx

### TargetConfiguration

**TargetConfiguration**: `Object`
**TargetConfiguration**<`T`\>: `Object`

#### Type parameters

| Name | Type |
| :--- | :---- |
| `T` | `any` |

---

Expand Down
2 changes: 1 addition & 1 deletion docs/generated/packages/devkit.json

Large diffs are not rendered by default.

34 changes: 33 additions & 1 deletion docs/generated/packages/nx-plugin.json
Expand Up @@ -50,7 +50,8 @@
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "tslint"],
"default": "eslint"
"default": "eslint",
"x-deprecated": "TSLint support is deprecated and will be removed"
},
"unitTestRunner": {
"type": "string",
Expand All @@ -73,6 +74,11 @@
"default": false,
"description": "Do not update tsconfig.json for development experience."
},
"skipLintChecks": {
"type": "boolean",
"default": false,
"description": "Do not eslint configuration for plugin json files."
},
"standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",
"type": "boolean"
Expand Down Expand Up @@ -315,6 +321,32 @@
"aliases": [],
"hidden": false,
"path": "/packages/nx-plugin/src/generators/executor/schema.json"
},
{
"name": "plugin-lint-checks",
"factory": "./src/generators/lint-checks/generator",
"schema": {
"$schema": "http://json-schema.org/schema",
"cli": "nx",
"$id": "PluginLint",
"title": "",
"type": "object",
"description": "Adds linting configuration to validate common json files for nx plugins.",
"properties": {
"projectName": {
"type": "string",
"description": "Which project should be the configuration be added to?",
"$default": { "$source": "projectName" }
}
},
"required": ["projectName"],
"presets": []
},
"description": "Adds linting configuration to validate common json files for nx plugins.",
"implementation": "/packages/nx-plugin/src/generators/lint-checks/generator.ts",
"aliases": [],
"hidden": false,
"path": "/packages/nx-plugin/src/generators/lint-checks/schema.json"
}
],
"executors": [
Expand Down
3 changes: 2 additions & 1 deletion docs/packages.json
Expand Up @@ -188,7 +188,8 @@
"e2e-project",
"migration",
"generator",
"executor"
"executor",
"plugin-lint-checks"
]
}
},
Expand Down
88 changes: 88 additions & 0 deletions e2e/nx-plugin/src/nx-plugin.test.ts
Expand Up @@ -185,6 +185,94 @@ describe('Nx Plugin', () => {
});
}, 90000);

it('should catch invalid implementations, schemas, and version in lint', async () => {
const plugin = uniq('plugin');
const goodGenerator = uniq('good-generator');
const goodExecutor = uniq('good-executor');
const goodMigration = uniq('good-migration');
const badMigrationVersion = uniq('bad-version');
const missingMigrationVersion = uniq('missing-version');

// Generating the plugin results in a generator also called {plugin},
// as well as an executor called "build"
runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=eslint`);

runCLI(
`generate @nrwl/nx-plugin:generator ${goodGenerator} --project=${plugin}`
);

runCLI(
`generate @nrwl/nx-plugin:executor ${goodExecutor} --project=${plugin}`
);

runCLI(
`generate @nrwl/nx-plugin:migration ${badMigrationVersion} --project=${plugin} --packageVersion="invalid"`
);

runCLI(
`generate @nrwl/nx-plugin:migration ${missingMigrationVersion} --project=${plugin} --packageVersion="0.1.0"`
);

runCLI(
`generate @nrwl/nx-plugin:migration ${goodMigration} --project=${plugin} --packageVersion="0.1.0"`
);

updateFile(`libs/${plugin}/generators.json`, (f) => {
const json = JSON.parse(f);
// @proj/plugin:plugin has an invalid implementation path
json.generators[plugin].factory = `./generators/${plugin}/bad-path`;
// @proj/plugin:non-existant has a missing implementation path amd schema
json.generators['non-existant-generator'] = {};
return JSON.stringify(json);
});

updateFile(`libs/${plugin}/executors.json`, (f) => {
const json = JSON.parse(f);
// @proj/plugin:build has an invalid implementation path
json.executors['build'].implementation = './executors/build/bad-path';
// @proj/plugin:non-existant has a missing implementation path amd schema
json.executors['non-existant-executor'] = {};
return JSON.stringify(json);
});

updateFile(`libs/${plugin}/migrations.json`, (f) => {
const json = JSON.parse(f);
delete json.generators[missingMigrationVersion].version;
return JSON.stringify(json);
});

const results = runCLI(`lint ${plugin}`, { silenceError: true });
expect(results).toContain(
`${plugin}: Implementation path should point to a valid file`
);
expect(results).toContain(
`non-existant-generator: Missing required property - \`schema\``
);
expect(results).toContain(
`non-existant-generator: Missing required property - \`implementation\``
);
expect(results).not.toContain(goodGenerator);

expect(results).toContain(
`build: Implementation path should point to a valid file`
);
expect(results).toContain(
`non-existant-executor: Missing required property - \`schema\``
);
expect(results).toContain(
`non-existant-executor: Missing required property - \`implementation\``
);
expect(results).not.toContain(goodExecutor);

expect(results).toContain(
`${missingMigrationVersion}: Missing required property - \`version\``
);
expect(results).toContain(
`${badMigrationVersion}: Version should be a valid semver`
);
expect(results).not.toContain(goodMigration);
});

describe('local plugins', () => {
const plugin = uniq('plugin');
beforeEach(() => {
Expand Down
1 change: 1 addition & 0 deletions nx.json
Expand Up @@ -20,6 +20,7 @@
"build-base",
"test",
"lint",
"lint-base",
"e2e",
"sitemap"
],
Expand Down
3 changes: 2 additions & 1 deletion packages/eslint-plugin-nx/package.json
Expand Up @@ -36,6 +36,7 @@
"@nrwl/workspace": "file:../workspace",
"@typescript-eslint/experimental-utils": "~5.24.0",
"chalk": "4.1.0",
"confusing-browser-globals": "^1.0.9"
"confusing-browser-globals": "^1.0.9",
"semver": "7.3.4"
}
}
5 changes: 5 additions & 0 deletions packages/eslint-plugin-nx/src/index.ts
Expand Up @@ -11,6 +11,10 @@ import enforceModuleBoundaries, {
RULE_NAME as enforceModuleBoundariesRuleName,
} from './rules/enforce-module-boundaries';

import nxPluginChecksRule, {
RULE_NAME as nxPluginChecksRuleName,
} from './rules/nx-plugin-checks';

// Resolve any custom rules that might exist in the current workspace
import { workspaceRules } from './resolve-workspace-rules';

Expand All @@ -27,6 +31,7 @@ module.exports = {
},
rules: {
[enforceModuleBoundariesRuleName]: enforceModuleBoundaries,
[nxPluginChecksRuleName]: nxPluginChecksRule,
...workspaceRules,
},
};
41 changes: 7 additions & 34 deletions packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts
Expand Up @@ -3,8 +3,6 @@ import {
joinPathFragments,
normalizePath,
ProjectGraphExternalNode,
readCachedProjectGraph,
readNxJson,
} from '@nrwl/devkit';
import {
DepConstraint,
Expand All @@ -21,10 +19,8 @@ import {
isAbsoluteImportIntoAnotherProject,
isAngularSecondaryEntrypoint,
isDirectDependency,
isTerminalRun,
MappedProjectGraph,
MappedProjectGraphNode,
mapProjectGraphFiles,
matchImportWithWildcard,
onlyLoadChildren,
stringifyTags,
Expand All @@ -40,13 +36,16 @@ import {
findFilesInCircularPath,
} from '@nrwl/workspace/src/utils/graph-utils';
import { isRelativePath } from '@nrwl/workspace/src/utilities/fileutils';
import * as chalk from 'chalk';
import { basename, dirname, relative } from 'path';
import {
getBarrelEntryPointByImportScope,
getBarrelEntryPointProjectNode,
getRelativeImportPath,
} from '../utils/ast-utils';
import {
ensureGlobalProjectGraph,
readProjectGraph,
} from '../utils/project-graph-utils';

type Options = [
{
Expand Down Expand Up @@ -148,40 +147,14 @@ export default createESLintRule<Options, MessageIds>({
const projectPath = normalizePath(
(global as any).projectPath || workspaceRoot
);
/**
* Only reuse graph when running from terminal
* Enforce every IDE change to get a fresh nxdeps.json
*/
if (!(global as any).projectGraph || !isTerminalRun()) {
const nxJson = readNxJson();
(global as any).workspaceLayout = nxJson.workspaceLayout;

/**
* Because there are a number of ways in which the rule can be invoked (executor vs ESLint CLI vs IDE Plugin),
* the ProjectGraph may or may not exist by the time the lint rule is invoked for the first time.
*/
try {
(global as any).projectGraph = mapProjectGraphFiles(
readCachedProjectGraph()
);
} catch {
const WARNING_PREFIX = `${chalk.reset.keyword('orange')('warning')}`;
const RULE_NAME_SUFFIX = `${chalk.reset.dim(
'@nrwl/nx/enforce-module-boundaries'
)}`;
process.stdout
.write(`${WARNING_PREFIX} No cached ProjectGraph is available. The rule will be skipped.
If you encounter this error as part of running standard \`nx\` commands then please open an issue on https://github.com/nrwl/nx
${RULE_NAME_SUFFIX}\n`);
}
}

if (!(global as any).projectGraph) {
const projectGraph = readProjectGraph(RULE_NAME);

if (!projectGraph) {
return {};
}

const workspaceLayout = (global as any).workspaceLayout;
const projectGraph = (global as any).projectGraph as MappedProjectGraph;

if (!(global as any).targetProjectLocator) {
(global as any).targetProjectLocator = new TargetProjectLocator(
Expand Down

1 comment on commit 10363e3

@vercel
Copy link

@vercel vercel bot commented on 10363e3 Jun 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-git-master-nrwl.vercel.app
nx-dev-nrwl.vercel.app
nx-five.vercel.app
nx.dev

Please sign in to comment.