Skip to content

Commit

Permalink
fix(angular): fix root-project support for Angular (#13534)
Browse files Browse the repository at this point in the history
  • Loading branch information
meeroslav committed Dec 1, 2022
1 parent 92df716 commit 22e70d6
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 59 deletions.
72 changes: 71 additions & 1 deletion e2e/linter/src/linter.test.ts
Expand Up @@ -442,7 +442,7 @@ export function tslibC(): string {
describe('Root projects migration', () => {
afterEach(() => cleanupProject());

it('should set root project config to app and e2e app and migrate when another lib is added', () => {
it('(React standalone) should set root project config to app and e2e app and migrate when another lib is added', () => {
const myapp = uniq('myapp');
const mylib = uniq('mylib');

Expand Down Expand Up @@ -506,6 +506,76 @@ export function tslibC(): string {
expect(libEslint.overrides[1].extends).toBeUndefined();
expect(libEslint.overrides[1].extends).toBeUndefined();
});

it('(Angular standalone) should set root project config to app and e2e app and migrate when another lib is added', () => {
const myapp = uniq('myapp');
const mylib = uniq('mylib');

newProject();
runCLI(`generate @nrwl/angular:app ${myapp} --rootProject=true`);

let rootEslint = readJson('.eslintrc.json');
let e2eEslint = readJson('e2e/.eslintrc.json');
expect(() => checkFilesExist(`.eslintrc.base.json`)).toThrow();

// should directly refer to nx plugin
expect(rootEslint.plugins).toEqual(['@nrwl/nx']);
expect(e2eEslint.plugins).toEqual(['@nrwl/nx']);
// should only extend framework plugin
expect(e2eEslint.extends).toEqual(['plugin:cypress/recommended']);
// should have plugin extends
expect(rootEslint.overrides[0].files).toEqual(['*.ts']);
expect(rootEslint.overrides[0].extends).toEqual([
'plugin:@nrwl/nx/typescript',
'plugin:@nrwl/nx/angular',
'plugin:@angular-eslint/template/process-inline-templates',
]);
expect(Object.keys(rootEslint.overrides[0].rules)).toEqual([
'@angular-eslint/directive-selector',
'@angular-eslint/component-selector',
]);
expect(rootEslint.overrides[1].files).toEqual(['*.html']);
expect(rootEslint.overrides[1].extends).toEqual([
'plugin:@nrwl/nx/angular-template',
]);
expect(e2eEslint.overrides[0].extends).toEqual([
'plugin:@nrwl/nx/typescript',
]);
expect(e2eEslint.overrides[1].extends).toEqual([
'plugin:@nrwl/nx/javascript',
]);

runCLI(`generate @nrwl/angular:lib ${mylib}`);
// should add new tslint
expect(() => checkFilesExist(`.eslintrc.base.json`)).not.toThrow();
const appEslint = readJson(`.eslintrc.json`);
rootEslint = readJson('.eslintrc.base.json');
e2eEslint = readJson('e2e/.eslintrc.json');
const libEslint = readJson(`libs/${mylib}/.eslintrc.json`);

// should directly refer to nx plugin only in the root
expect(rootEslint.plugins).toEqual(['@nrwl/nx']);
expect(appEslint.plugins).toBeUndefined();
expect(e2eEslint.plugins).toBeUndefined();
// should extend framework plugin and root config
expect(appEslint.extends).toEqual(['./.eslintrc.base.json']);
expect(e2eEslint.extends).toEqual([
'plugin:cypress/recommended',
'../.eslintrc.base.json',
]);
expect(libEslint.extends).toEqual(['../../.eslintrc.base.json']);
// should have no plugin extends
expect(appEslint.overrides[0].extends).toEqual([
'plugin:@nrwl/nx/angular',
'plugin:@angular-eslint/template/process-inline-templates',
]);
expect(e2eEslint.overrides[0].extends).toBeUndefined();
expect(e2eEslint.overrides[1].extends).toBeUndefined();
expect(libEslint.overrides[0].extends).toEqual([
'plugin:@nrwl/nx/angular',
'plugin:@angular-eslint/template/process-inline-templates',
]);
});
});
});

Expand Down
19 changes: 4 additions & 15 deletions packages/angular/src/generators/add-linting/add-linting.spec.ts
Expand Up @@ -25,16 +25,16 @@ describe('addLinting generator', () => {
} as ProjectConfiguration);
});

it('should invoke the lint init generator', async () => {
jest.spyOn(linter, 'lintInitGenerator');
it('should invoke the lintProjectGenerator', async () => {
jest.spyOn(linter, 'lintProjectGenerator');

await addLintingGenerator(tree, {
prefix: 'myOrg',
projectName: appProjectName,
projectRoot: appProjectRoot,
});

expect(linter.lintInitGenerator).toHaveBeenCalled();
expect(linter.lintProjectGenerator).toHaveBeenCalled();
});

it('should add the Angular specific EsLint devDependencies', async () => {
Expand Down Expand Up @@ -79,18 +79,7 @@ describe('addLinting generator', () => {
`${appProjectRoot}/**/*.html`,
],
},
outputs: ['{options.outputFile}'],
});
});

it('should format files', async () => {
jest.spyOn(devkit, 'formatFiles');

await addLintingGenerator(tree, {
prefix: 'myOrg',
projectName: appProjectName,
projectRoot: appProjectRoot,
});

expect(devkit.formatFiles).toHaveBeenCalled();
});
});
50 changes: 34 additions & 16 deletions packages/angular/src/generators/add-linting/add-linting.ts
@@ -1,33 +1,51 @@
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import { formatFiles } from '@nrwl/devkit';
import { Linter, lintInitGenerator } from '@nrwl/linter';
import {
GeneratorCallback,
joinPathFragments,
Tree,
updateJson,
} from '@nrwl/devkit';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { mapLintPattern } from '@nrwl/linter/src/generators/lint-project/lint-project';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { addAngularEsLintDependencies } from './lib/add-angular-eslint-dependencies';
import { addProjectLintTarget } from './lib/add-project-lint-target';
import { createEsLintConfiguration } from './lib/create-eslint-configuration';
import { extendAngularEslintJson } from './lib/create-eslint-configuration';
import type { AddLintingGeneratorSchema } from './schema';

export async function addLintingGenerator(
tree: Tree,
options: AddLintingGeneratorSchema
): Promise<GeneratorCallback> {
const installTask = lintInitGenerator(tree, {
const tasks: GeneratorCallback[] = [];
const rootProject = options.projectRoot === '.' || options.projectRoot === '';
const lintTask = await lintProjectGenerator(tree, {
linter: Linter.EsLint,
project: options.projectName,
tsConfigPaths: [
joinPathFragments(options.projectRoot, 'tsconfig.app.json'),
],
unitTestRunner: options.unitTestRunner,
skipPackageJson: options.skipPackageJson,
eslintFilePatterns: [
mapLintPattern(options.projectRoot, 'ts', rootProject),
mapLintPattern(options.projectRoot, 'html', rootProject),
],
setParserOptionsProject: options.setParserOptionsProject,
skipFormat: true,
rootProject: rootProject,
});
tasks.push(lintTask);

if (!options.skipPackageJson) {
addAngularEsLintDependencies(tree);
}
updateJson(
tree,
joinPathFragments(options.projectRoot, '.eslintrc.json'),
(json) => extendAngularEslintJson(json, options)
);

createEsLintConfiguration(tree, options);
addProjectLintTarget(tree, options);

if (!options.skipFormat) {
await formatFiles(tree);
if (!options.skipPackageJson) {
const installTask = await addAngularEsLintDependencies(tree);
tasks.push(installTask);
}

return installTask;
return runTasksInSerial(...tasks);
}

export default addLintingGenerator;
@@ -1,9 +1,9 @@
import type { Tree } from '@nrwl/devkit';
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import { addDependenciesToPackageJson } from '@nrwl/devkit';
import { angularEslintVersion } from '../../../utils/versions';

export function addAngularEsLintDependencies(tree: Tree): void {
addDependenciesToPackageJson(
export function addAngularEsLintDependencies(tree: Tree): GeneratorCallback {
return addDependenciesToPackageJson(
tree,
{},
{
Expand Down
Expand Up @@ -3,19 +3,21 @@ import {
readProjectConfiguration,
updateProjectConfiguration,
} from '@nrwl/devkit';
import { mapLintPattern } from '@nrwl/linter/src/generators/lint-project/lint-project';
import type { AddLintingGeneratorSchema } from '../schema';

export function addProjectLintTarget(
tree: Tree,
options: AddLintingGeneratorSchema
): void {
const project = readProjectConfiguration(tree, options.projectName);
const rootProject = options.projectRoot === '.' || options.projectRoot === '';
project.targets.lint = {
executor: '@nrwl/linter:eslint',
options: {
lintFilePatterns: [
`${options.projectRoot}/**/*.ts`,
`${options.projectRoot}/**/*.html`,
mapLintPattern(options.projectRoot, 'ts', rootProject),
mapLintPattern(options.projectRoot, 'html', rootProject),
],
},
};
Expand Down
@@ -1,8 +1,65 @@
import type { Tree } from '@nrwl/devkit';
import { joinPathFragments, offsetFromRoot, writeJson } from '@nrwl/devkit';
import { camelize, dasherize } from '@nrwl/workspace/src/utils/strings';
import type { Linter } from 'eslint';
import type { AddLintingGeneratorSchema } from '../schema';

type EslintExtensionSchema = {
prefix: string;
};

export const extendAngularEslintJson = (
json: Linter.Config,
options: EslintExtensionSchema
) => {
const overrides = [
{
...json.overrides[0],
files: ['*.ts'],
extends: [
...(json.overrides[0].extends || []),
'plugin:@nrwl/nx/angular',
'plugin:@angular-eslint/template/process-inline-templates',
],
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: camelize(options.prefix),
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: dasherize(options.prefix),
style: 'kebab-case',
},
],
},
},
{
files: ['*.html'],
extends: ['plugin:@nrwl/nx/angular-template'],
/**
* Having an empty rules object present makes it more obvious to the user where they would
* extend things from if they needed to
*/
rules: {},
},
];

return {
...json,
overrides,
};
};

/**
* @deprecated Use {@link extendAngularEslintJson} instead
*/
export function createEsLintConfiguration(
tree: Tree,
options: AddLintingGeneratorSchema
Expand Down
Expand Up @@ -191,6 +191,9 @@ Object {
"apps/my-dir/my-app/**/*.html",
],
},
"outputs": Array [
"{options.outputFile}",
],
},
"serve": Object {
"builder": "@angular-devkit/build-angular:dev-server",
Expand Down Expand Up @@ -359,6 +362,9 @@ Object {
"apps/my-app/**/*.html",
],
},
"outputs": Array [
"{options.outputFile}",
],
},
"serve": Object {
"builder": "@angular-devkit/build-angular:dev-server",
Expand Down
Expand Up @@ -544,6 +544,9 @@ describe('app', () => {
"apps/my-app/**/*.html",
],
},
"outputs": Array [
"{options.outputFile}",
],
}
`);
expect(workspaceJson.projects['my-app-e2e'].architect.lint)
Expand Down Expand Up @@ -578,6 +581,9 @@ describe('app', () => {
"apps/my-app/**/*.html",
],
},
"outputs": Array [
"{options.outputFile}",
],
}
`);
expect(appTree.exists('apps/my-app-e2e/.eslintrc.json')).toBeTruthy();
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/src/generators/application/application.ts
Expand Up @@ -104,7 +104,7 @@ export async function applicationGenerator(
addRouterRootConfiguration(host, options);
}

addLinting(host, options);
await addLinting(host, options);
await addUnitTestRunner(host, options);
await addE2e(host, options);
updateEditorTsConfig(host, options);
Expand Down
Expand Up @@ -624,6 +624,9 @@ Object {
"apps/angular-app-1/**/*.html",
],
},
"outputs": Array [
"{options.outputFile}",
],
},
},
}
Expand Down Expand Up @@ -993,6 +996,9 @@ Object {
"libs/angular-lib-1/**/*.html",
],
},
"outputs": Array [
"{options.outputFile}",
],
},
},
}
Expand Down
Expand Up @@ -351,3 +351,24 @@ import { myLibRoutes } from '@proj/my-lib';
export const appRoutes: Route[] = [
{ path: 'my-lib', children: myLibRoutes },]"
`;
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup and attach it to standalone parent routes as a lazy child 1`] = `
"import { Route } from '@angular/router';
import { MyLibComponent } from './my-lib/my-lib.component';
export const myLibRoutes: Route[] = [
{path: 'second', loadChildren: () => import('@proj/second').then(m => m.secondRoutes)},
{path: '', component: MyLibComponent}
]"
`;
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup and attach it to standalone parent routes as direct child 1`] = `
"import { Route } from '@angular/router';
import { MyLibComponent } from './my-lib/my-lib.component';
import { secondRoutes } from '@proj/second';
export const myLibRoutes: Route[] = [
{ path: 'second', children: secondRoutes },
{path: '', component: MyLibComponent}
]"
`;

0 comments on commit 22e70d6

Please sign in to comment.